当下10大流行的JavaScript动画库,看一看有你喜欢的吗?
我喜欢去尝试各种动画效果,尤其是使用CSS实现简单的动画。但是,如果从长远来看,只会使用CSS实现动画让我们走不远。而且,若你想实现复杂的交互式用户体验,你早晚要用到 JavaScript。
使用 JavaScript实现动画效果的主要优势在于你能够控制更多的动画逻辑,包括动画转换的流动性、控制DOM的状态和响应以及使用WebGL实现2D和3D图形。
JS动画库有许多类型,它确实有助于帮助缩小我们关注的领域。并且很多引擎和框架不仅仅用于前端,同时还用于游戏和其他交互式内容。所以需要注意,我们该篇文章选择的JS动画库是前端开发中常用的库。
#1: Three.js
Three.js是开发有创意性的网站中实现3D动画效果首选的库。它让开发人员不需要花费大量时间去学习WebGL,可以轻松的实现具有交互式的3D效果。
Three.js 可用于构建交互式虚拟体验,例如Mozilla Hubs。此外,该库还经常用于创建沉浸式的加载页面体验。
下面是用Three.js实现动画效果的实例:
html:
<div id="main"></div>
css:
#main {
position: fixed;
width: 100vw;
top: 0;
left: 0;
height: 100vh;
overflow: hidden;
}
js:
var background_waves_background_color = "#212121";
var background_waves_mesh_color = "#3B4246";
function wavesBackground() {
var vertex_height = 15000,
plane_segments_size = 100,
plane_size = 1245000,
background_color = background_waves_background_color,
mesh_color = background_waves_mesh_color;
var container = document.getElementById('main'),
inital_Z = [],
count = 0;
var camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 400000)
camera.position.z = 10000;
camera.position.y = 10000;
var scene = new THREE.Scene();
scene.fog = new THREE.Fog(background_color, 1, 300000);
var geometry = new THREE.PlaneGeometry(plane_size, plane_size, plane_segments_size, plane_segments_size);
var mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: mesh_color,
wireframe: !0
}));
mesh.rotation.x -= Math.PI * .5;
scene.add(mesh);
var renderer = new THREE.WebGLRenderer({
alpha: !1
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(background_color, 1);
container.appendChild(renderer.domElement);
function updateGeometry() {
for (var i = 0; i < geometry.attributes.position.count; i++) {
var z = Math.random() * vertex_height - vertex_height;
geometry.attributes.position.setZ(i, z);
inital_Z[i] = z
}
};
updateGeometry();
function render() {
requestAnimationFrame(render);
var x = camera.position.x;
var z = camera.position.z;
camera.position.x = x * Math.cos(0.001) + z * Math.sin(0.001) - 10;
camera.position.z = z * Math.cos(0.001) - x * Math.sin(0.001) - 10;
camera.lookAt(new THREE.Vector3(0, 9000, 0))
for (var i = 0; i < geometry.attributes.position.count; i++) {
var next_z = Math.sin((i + count * 0.00002)) * (inital_Z[i] - (inital_Z[i] * 0.6));
if (next_z !== next_z) return;
geometry.attributes.position.setZ(i, next_z);
geometry.attributes.position.needsUpdate = !0;
count += 0.075
}
renderer.render(scene, camera)
}
render();
function resize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight)
}
window.addEventListener('resize', resize, !1)
};
wavesBackground();
#2: Anime.js
Julian Garnier 的 Anime.js 可能是第二个最知名的在Web项目中实现动画效果的库。它有内置工具帮助加快CSS、SVG 和 DOM 元素的动画制作过程。
例如,你可以针对特定的CSS选择器使用JavaScript改进动画逻辑,而不是你自己写@keyframes
。
anime({
targets: '.staggering-axis-grid-demo .el',
translateX: anime.stagger(10, {grid: [14, 5], from: 'center', axis: 'x'}),
translateY: anime.stagger(10, {grid: [14, 5], from: 'center', axis: 'y'}),
rotateZ: anime.stagger([0, 90], {grid: [14, 5], from: 'center', axis: 'x'}),
delay: anime.stagger(200, {grid: [14, 5], from: 'center'}),
easing: 'easeInOutQuad'
});
仅使用上面的代码片段,就可以实现如下所示的效果:
#3: Popmotion
Popmotion 是一个用 TypeScript 编写的动画库。它也是为React项目中使用的Framer Motion(下文会介绍)库提供支持的库。
从它的名字我们可以看出,Popmotion特别擅长通过使用各种基于运动的动画来制作动画。包括缓动、弹跳、关键帧和更复杂的过渡效果等。我们来看一个实例:
Pug:
.wrapper
.circle.circle--01 tween
.circle.circle--02 drag
.circle.circle--03 tap
.circle.circle--04 :hover
.circle.circle--05
SCSS:
.wrapper {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.circle {
width: 100px;
height: 100px;
border-radius: 100px;
margin: 0 2em;
display: flex;
justify-content: center;
color: #fff;
font-weight: 700;
align-items: center;
&--01 {
background: blue;
}
&--02 {
background: red;
cursor: grab;
&:hover {
}
&:active {
cursor: grabbing;
}
}
&--03 {
background: green;
&:hover {
cursor: pointer;
}
}
&--04 {
background: black;
}
&--05 {
background: purple;
}
}
Babel:
const {
css,
listen,
timeline,
spring,
easing,
value,
tween,
styler,
parallel,
physics,
pointer,
transform,
} = window.popmotionXL;
const { pipe, clampMax } = transform;
const circles = Array.from(document.querySelectorAll('.circle'));
const circle1 = styler(circles[0]);
const circle2 = styler(circles[1]);
const circle3 = styler(circles[2]);
const circle4 = styler(circles[3]);
const circle5 = styler(circles[4]);
tween({
from: { opacity: 1, scale: 1},
to: { opacity: 0.5, scale: 1.5},
duration: 1000,
flip: Infinity,
ease: easing.linear,
}).start(circle1.set);
const ballXY = value({ x: 0, y: 0 }, circle2.set);
listen(circles[1], 'mousedown touchstart')
.start((e) => {
e.preventDefault();
pointer(ballXY.get()).start(ballXY);
});
listen(document, 'mouseup touchend')
.start(() => {
spring({
from: ballXY.get(),
velocity: ballXY.getVelocity(),
to: { x: 0, y: 0 },
stiffness: 500,
mass: 1,
damping: 10
}).start(ballXY);
});
const ballStyler = circle3;
const ballY = value(0, (v) => ballStyler.set('y', Math.min(0, v)));
const ballScale = value(1, (v) => {
ballStyler.set('scaleX', 1 + (1 - v));
ballStyler.set('scaleY', v);
});
let count = 0;
let isFalling = false;
const ballBorder = value({
borderColor: '',
borderWidth: 0
}, ({ borderColor, borderWidth }) => ballStyler.set({
boxShadow: `0 0 0 ${borderWidth}px ${borderColor}`
}));
const checkBounce = () => {
if (!isFalling || ballY.get() < 0) return;
isFalling = false;
const impactVelocity = ballY.getVelocity();
const compression = spring({
to: 1,
from: 1,
velocity: - impactVelocity * 0.01,
stiffness: 800
}).pipe((s) => {
if (s >= 1) {
s = 1;
compression.stop();
if (impactVelocity > 20) {
isFalling = true;
gravity
.set(0)
.setVelocity(- impactVelocity * 0.5);
}
}
return s;
}).start(ballScale);
};
const checkFail = () => {
if (ballY.get() >= 0 && ballY.getVelocity() !== 0 && circles[2].innerHTML !== 'Tap') {
count = 0;
tween({
from: { borderWidth: 0, borderColor: 'rgb(255, 28, 104, 1)' },
to: { borderWidth: 30, borderColor: 'rgb(255, 28, 104, 0)' }
}).start(ballBorder);
circles[2].innerHTML = 'Tap';
}
};
const gravity = physics({
acceleration: 2500,
restSpeed: false
}).start((v) => {
ballY.update(v);
checkBounce(v);
checkFail(v);
});
listen(circles[2], 'mousedown touchstart').start((e) => {
e.preventDefault();
count++;
circles[2].innerHTML = count;
isFalling = true;
ballScale.stop();
ballScale.update(1);
gravity
.set(Math.min(0, ballY.get()))
.setVelocity(-1200);
tween({
from: { borderWidth: 0, borderColor: 'rgb(20, 215, 144, 1)' },
to: { borderWidth: 30, borderColor: 'rgb(20, 215, 144, 0)' }
}).start(ballBorder);
});
circles[3].addEventListener('mouseenter', () => {
spring({
from: 1,
to: 2,
stiffness: 500,
mass: 1,
damping: 10
}).start(circle4.set('scale'));
});
circles[3].addEventListener('mouseleave', () => {
spring({
from: 2,
to: 1,
stiffness: 500,
mass: 1,
damping: 10
}).start(circle4.set('scale'));
})
#4: mo.js
Mo.js 专注于动态图形,比较简洁。您可以在自定义项目中实现,也可以与 React 等框架一起使用它。
因为 Mo.js 拥有声明性 API——你可以控制动画的每一步。这不仅可以实现希望实现的逻辑,还可以控制如何实现目标。
HTML:
<div class="container">
<h1>Hover My Links</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. <a href="">Voluptates</a> illum saepe, placeat ut <a href="">delectus</a> minima officiis. Inventore mollitia sapiente, aut est nesciunt perspiciatis, odio sunt ad <a href="">natus</a> labore, enim quod.</p>
</div>
SCSS:
body {
font-family: lato, sans-serif;
font-size: 16px;
line-height: 1.2em;
}
body {
display: flex;
height: 100vh;
align-items: center;
background-image: linear-gradient(-225deg, #efefef 0%, #ffffff 100%);
}
.container {
width: 50%;
margin: 0 auto;
text-align: center;
@media screen and (max-width: 700px) {
width: 75%;
}
}
h1 {
position: relative;
font-family: cardo, serif;
font-size: 2.6em;
font-weight: 700;
color: #333333;
line-height: 1.2em;
letter-spacing: 0.03em;
margin-bottom: 1.66em;
}
h1:after {
content: '†';
display: block;
position: absolute;
left: 50%;
top: 100%;
transform: rotateZ(180deg);
}
p {
font-size: 1.2em;
line-height: 1.4em;
color: #333333;
}
a {
display: inline-block;
position: relative;
color: #333333;
text-decoration: none;
padding: 0.2em 0.05em;
border-bottom: 2px solid #333333;
}
a:hover {
color: #efefef;
}
a:after {
content: '';
display: block;
position: absolute;
top: 100%;
right: 0;
bottom: 0;
left: 0;
background: #333333;
z-index: -1;
transition: all 0.1s cubic-bezier(0.000, 0.590, 1.000, 0.260);
}
a:hover:after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #333333;
z-index: -1;
}
Babel:
const links = document.querySelectorAll('a');
links.forEach(link => link.addEventListener('mouseenter', shootLines));
function shootLines(e) {
const itemDim = this.getBoundingClientRect(),
itemSize = {
x: itemDim.right - itemDim.left,
y: itemDim.bottom - itemDim.top,
},
shapes = ['line', 'zigzag'],
colors = ['#2FB5F3',
'#FF0A47',
'#FF0AC2',
'#47FF0A'];
const chosenC = Math.floor(Math.random() * colors.length),
chosenS = Math.floor(Math.random() * shapes.length);
// create shape
const burst = new mojs.Burst({
left: itemDim.left + (itemSize.x/2),
top: itemDim.top + (itemSize.y/2),
radiusX: itemSize.x,
radiusY: itemSize.y,
count: 8,
children: {
shape: shapes[chosenS],
radius: 10,
scale: {0.8: 1},
fill: 'none',
points: 7,
stroke: colors[chosenC],
strokeDasharray: '100%',
strokeDashoffset: { '-100%' : '100%' },
duration: 350,
delay: 100,
easing: 'quad.out',
isShowEnd: false,
}
});
burst.play();
}
#5: p5.js
p5.js 是 Processing 的 JavaScript 实现。Processing是一种供视觉艺术家使用的独立“语言”。与我们之前看到的那些示例不同,p5.js 是一个通用动画库,不仅为实际应用提供解决方案,还为更健壮和复杂的项目提供解决方案。这包括对 2D 和 2D 效果的全面支持。
通过使用该库,你可以快速的实现烟幕、动画树和用户可以与之交互的基于数据的加载页面。
p5.js 示例动画:
CSS:
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
JS:
let t = 0,
w = 320, colors = ["#774F38","#E08E79","#F1D4AF","#ECE5CE","#C5E0DC"]
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
}
function draw() {
blendMode(t <0.5 ? SCREEN : ADD)
translate(-width / 2, -height / 2);
t += sin(radians(frameCount)) * 0.01;
for (let j = 0; j < 8; j++) {
let n = 0
for (let i = 0; i < 8; i++) {
let m = 64 * j;
push();
translate(16, -16, t * 100);
translate(64 * i, m, 0);
rotate(TAU * t);
scale(0.32+t)
let c = color(colors[n%(colors.length-1)])
c.setAlpha(32)
fill(c);
let cc = color(colors[colors.length-1-(n%(colors.length-1))])
cc.setAlpha(128)
stroke(cc);
box(32)
push()
translate(-32, 32, 0)
box(16)
pop()
push()
translate(32, -32, 0)
box(16)
pop()
push()
translate(-32, -32, 0)
box(16)
pop()
push()
translate(32, 32, 0)
box(16)
pop()
pop();
n++
}
}
}
#6: Motion
Framer Motion是React的动画库...
其带有预构建的 API,可让 React 开发人员简化构建动画组件的过程,同时也减轻了学习 CSS 及其动画属性的一些障碍,特别是它很容易使用。
HTML:
<div id="root"></div>
CSS:
body {
display: flex;
align-items: center;
justify-content: center;
background-color: #212121;
color: #FAFAFA;
margin: 0;
}
button {
margin: 0;
padding: 0;
border: none;
background: none;
font-family: Orbitron, Arial, sans-serif;
cursor: pointer;
}
button:focus {
outline: none;
}
svg {
display: block;
}
span {
position: relative;
bottom: -2px;
}
.dot {
position: absolute;
left: -5px;
top: -5px;
width: 10px;
height: 10px;
opacity: 0;
}
.button {
position: relative;
display: flex;
flex-direction: row;
padding: 28px 48px;
margin-top: 80px;
color: #FAFAFA;
border: 4px solid currentColor;
font-size: 64px;
}
Babel:
const { motion, AnimatePresence } = Motion;
const { useState } = React;
function App() {
const [mouse, setMouse] = useState([0,0,false]);
const [mx, my, isActive] = mouse;
return (
<motion.button
className="button"
whileHover={{
backgroundColor: "#424242"
}}
whileTap={{
backgroundColor: "#212121"
}}
onMouseMove={e => {
const { offsetTop, offsetLeft } = e.currentTarget;
setMouse([e.pageX - offsetLeft, e.pageY - offsetTop, true]);
}}
onMouseEnter={() => setMouse([mx, my, true])}
onMouseLeave={() => setMouse([mx, my, false])}
>
<AnimatePresence>
{isActive && (
<motion.div
key="dot"
className="dot"
initial={{
opacity: 0,
}}
animate={{
x: mx,
y: my,
opacity: 1,
}}
exit={{
opacity: 0,
}}
>
<svg width="10" height="10" viewBox="0 0 10 10">
<circle fill="red" cx="4" cy="4" r="4" />
</svg>
</motion.div>
)}
</AnimatePresence>
<span>Hello World</span>
</motion.button>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
#7: GSAP
该库不仅针对性能进行了优化,而且兼容性很好——兼容React和Vue,还有jQuery等,甚至支持移动端和旧版本浏览器。
GSAP 能够查询和动画化几乎任何类型的 Web 元素(从 CSS 到 Canvas 到 DOM 对象),我们可以将它实现像微调器效果这样简单的事情,也可以实现更复杂的效果。
HTML:
<div id="green-filter"></div>
<div id="top-gradient" class="gradient"></div>
<div id="bottom-gradient" class="gradient"></div>
<div id="logo-wrapper">
<div id="logo">
<div id="logo-border" class="absolute-centered"></div>
<div id="logo-border-inner" class="absolute-centered"></div>
<div id="logo-text">
<div id="hulu-text" class="zen-dots-font">hulu</div>
<div id="originals-text" class="jakarta-sans-font">ORIGINALS</div>
</div>
</div>
</div>
<button id="restart-button" class="rubik-font" type="button">Restart</button>
<a id="youtube-link" href="https://youtu.be/4q-uG5VeEl4" target="_blank">
<i class="fa-brands fa-youtube"></i>
<span class="rubik-font">Watch Me Code It</span>
</a>
CSS:
:root {
--background-color: rgb(10, 10, 10);
--hulu-color: rgb(27, 219, 124);
--gradient-green-rgb: 32, 147, 127;
--gradient-blue-rgb: 127, 117, 237;
--gradient-violet-rgb: 171, 111, 218;
--highlight-blue-rgb: 45, 37, 143;
}
body{
align-items: center;
background-color: var(--background-color);
display: flex;
height: 100vh;
justify-content: center;
margin: 0px;
overflow: hidden;
padding: 0px;
width: 100vw;
}
.absolute-centered {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
.jakarta-sans-font {
font-family: "Plus Jakarta Sans", sans-serif;
}
.zen-dots-font {
font-family: "Zen Dots", cursive;
}
.rubik-font {
font-family: "Rubik", sans-serif;
font-weight: 500;
}
#restart-button {
backdrop-filter: blur(3px);
background-color: rgba(255, 255, 255, 0.05);
border: none;
border-radius: 6px;
bottom: 10px;
color: white;
cursor: pointer;
font-size: 0.9em;
left: 50%;
outline: none;
padding: 10px 20px;
position: absolute;
transform: translateX(-50%);
z-index: 100;
}
#restart-button:hover,
#restart-button:focus {
background-color: rgba(255, 255, 255, 0.1);
}
#green-filter {
background: radial-gradient(rgba(var(--gradient-green-rgb), 0.05), rgba(var(--gradient-green-rgb), 0.4) 80%);
height: 100%;
left: 0px;
position: absolute;
top: 0px;
width: 100%;
z-index: 1;
}
.gradient {
filter: blur(3em);
height: 80px;
left: -5%;
position: absolute;
width: 110%;
}
#top-gradient {
background: linear-gradient(
to right,
rgba(var(--gradient-blue-rgb), 0.75) 0% 10%,
transparent 10% 20%,
rgba(var(--gradient-violet-rgb), 0.5) 20% 50%,
rgba(var(--gradient-blue-rgb), 0.5) 50% 70%,
rgba(var(--gradient-green-rgb), 0.75) 70%
);
top: -50px;
}
#bottom-gradient {
background: linear-gradient(
to right,
rgba(var(--gradient-blue-rgb), 0.75) 0% 10%,
transparent 10% 30%,
rgba(var(--gradient-blue-rgb), 0.5) 30% 50%,
transparent 50% 70%,
rgba(var(--gradient-violet-rgb), 0.5) 70% 80%,
transparent 80%
);
bottom: -50px;
}
#logo-wrapper {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
width: 100vw;
}
#logo {
opacity: 0;
position: relative;
z-index: 2;
}
#logo-border {
background-color: var(--hulu-color);
border-radius: 2.25em;
height: 160%;
width: 140%;
z-index: 1;
}
#logo-border-inner {
background-color: var(--background-color);
border-radius: 2em;
height: calc(160% - 0.5em);
width: calc(140% - 0.5em);
z-index: 2;
}
#logo-text {
position: relative;
z-index: 3;
}
#hulu-text {
color: var(--hulu-color);
font-size: 8em;
height: 120px;
line-height: 120px;
}
#originals-text {
color: white;
font-size: 3em;
letter-spacing: 0.12em;
}
#youtube-link {
align-items: center;
background-color: rgba(255, 255, 255, 0.03);
border-radius: 4px;
bottom: 0px;
display: flex;
gap: 10px;
left: 0px;
margin: 10px;
padding: 10px;
position: absolute;
text-decoration: none;
z-index: 100;
}
#youtube-link:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#youtube-link:not(:hover) {
animation: bounce 6s infinite;
animation-delay: 5s;
}
#youtube-link i, #youtube-link span {
height: 16px;
line-height: 16px;
}
#youtube-link i {
color: rgb(237, 66, 69);
font-size: 0.9em;
}
#youtube-link span {
color: white;
font-size: 0.9em;
font-weight: 500;
}
@keyframes bounce {
from, 3.33%, 8.83%, 16.66% {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transform: translate3d(0, 0, 0);
}
6.66%, 7.16% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -15px, 0) scaleY(1.1);
}
11.66% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -7px, 0) scaleY(1.05);
}
13.33% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transform: translate3d(0, 0, 0) scaleY(0.95);
}
15% {
transform: translate3d(0, -2px, 0) scaleY(1.02);
}
}
@media(max-width: 800px) {
#logo-wrapper {
transform: scale(0.7);
}
}
@media(max-width: 600px) {
#restart-button {
left: auto;
right: 10px;
transform: none;
}
}
JS:
const hulu = new SplitText("#hulu-text"),
originals = new SplitText("#originals-text");
const t1 = new gsap.timeline();
t1.from(["#top-gradient", "#bottom-gradient"], 1, { ease: "power3.inOut", filter: "blur(0px)", opacity: 0 })
.from("#green-filter", 1.1, { opacity: 0, scale: 3 }, "-=50%")
.to("#green-filter", 0.3, { opacity: 0, scale: 3 })
.to(["#top-gradient", "#bottom-gradient"], 0.3, { filter: "blur(0px)", opacity: 0 }, "-=100%")
.set("#logo", { opacity: 1 })
.from(hulu.chars, 0.2, { ease: "back", filter: "blur(0.3em)", opacity: 0, scale: 1.5, stagger: 0.02 })
.from(originals.chars, 0.2, { delay: 0.25, filter: "blur(0.3em)", opacity: 0, scale: 0.5, stagger: 0.02, xPercent: -25 })
.from("#logo-border", 0.4, { ease: "power3.out", opacity: 0, scale: 0.75 }, "-=100%")
.from("#logo-border-inner", 0.4, { ease: "power3.out", scale: 0.75 }, "-=100%")
.to("#logo", 1.5, { scale: 1.1 }, "-=20%")
.to(["#logo-border", "#logo-border-inner"], 1.5, { ease: "power3.out", scale: 1.1 }, "-=100%")
.to("#logo-border", 1.25, { ease: "power4.in", scale: 8 }, "-=50%")
.to("#logo-border-inner", 0.5, { ease: "power4.in", scale: 8 }, "-=60%")
.to("#logo", 0.25, { opacity: 0, scale: 1.2 }, "-=50%");
document.getElementById("restart-button").onclick = () => t1.restart();
#8: Paper.js
Paper.js 专注于矢量图形动画领域。让您不仅可以制作静态效果,还可以制作交互式动态体验。可以通过层对对象进行分类,每个层都有一个自定义动画规范,当处理复杂的结构时,这能很好的帮助我们。
HTML:
<section class="--center --full-width" data-name="s-02">
<div class="glasses --container">
<div class="glass-container">
<div class="glass ">
<div class="glass-material --opacity --b-orange"></div>
<canvas id="glass-orange" resize></canvas>
</div>
</div>
<div class="glass-container">
<div class="glass">
<div class="glass-material --opacity --b-yellow"></div>
<canvas id="glass-yellow" resize></canvas>
</div>
</div>
<div class="glass-container">
<div class="glass ">
<div class="glass-material --opacity --b-lime"></div>
<canvas id="glass-lime" resize></canvas>
</div>
</div>
</div>
</section>
CSS:
$orange: #FDB702;
$d-orange: #FA8F01;
$yellow: #F7DA56;
$d-yellow: #F0D000;
$dd-yellow: #E4D956;
$lime: #C9DD2E;
$d-lime: #BDD50B;
$dd-lime: #86B139;
$brBottom50: 0 0 50px 50px;
.--center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.--full-width {
height: 100vh;
width: 100vw;
padding: 0;
}
.--container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
max-width: 851px;
margin-top: 4.375rem;
}
.--opacity {
opacity: 0.5;
}
.--b-orange {
background-color: $d-orange;
}
.--b-yellow {
background-color: $dd-yellow;
}
.--b-lime {
background-color: $d-lime;
}
@mixin box-size {
position: relative;
padding: 2rem;
width: 200px;
height: 200px;
}
// Section 02 Style
.glass-container {
@include box-size;
.glass {
width: 100%;
height: 250px;
position: relative;
.glass-material,
canvas {
width: 100%;
height: 100%;
border-radius: $brBottom50;
position: absolute;
}
canvas {
background-color: transparent;
}
}
}
JS:
// Paper graphics design
const color = ["#FA8F01", "#E4D956", "#BDD50B"];
const glassOrange = document.querySelector('#glass-orange');
const glassYellow = document.querySelector('#glass-yellow');
const glassLime = document.querySelector('#glass-lime');
paper.install(window);
const waveAmount = 3;
const y = 30;
let glasses = [];
function draw(n, color) {
console.log('working')
let width = glasses[n].view.size.width;
let height = glasses[n].view.size.height;
let waveHeight = 0;
let path = new Path({
fillColor: color
});
for (let i = 0; i <= waveAmount; i++) {
path.add(new Point((i / waveAmount) * width, y));
}
path.add(new Point(width, height));
path.add(new Point(width * 0, height));
path.selected = false;
path.closed = true;
glasses[n].view.onFrame = (event) => {
for (let i = 0; i <= waveAmount; i++) {
let segment = path.segments[i];
let sinus = Math.sin(event.time * 3 + i);
let eventTime = height - (event.time * 10);
waveHeight += 0.007;
if (waveHeight >= y) waveHeight = y / 2;
if (eventTime <= y) {
eventTime = y;
waveHeight = waveHeight - 0.008;
if (waveHeight < 0.01) {
glasses[n].view.pause();
}
}
segment.point.y = sinus * waveHeight + eventTime;
for (let s = 0; s < waveAmount; s++) {
if (s > 0 && s < waveAmount) {
path.segments[s].smooth();
}
}
}
}
}
const glass = [glassOrange, glassYellow, glassLime]
for (let i = 0; i <= 2; i++) {
glasses[i] = new PaperScope();
glasses[i].setup(glass[i]);
draw(i, color[i]);
}
#9: Web Animations
Web Animations库是和Web Animation API的直接的JS接口。该库直接与Element.animate()
规范集成,让您可以实现通常使用 CSS 编写的动画功能。
动画实现示例:
HTML:
<div class="ball-layer" id="layer">
</div>
<div class="dialog">
<h2>Imperative Animations</h2>
<p>
The <a target="_blank" href="https://github.com/web-animations/web-animations-js">Web Animations API</a> provides an imperative approach to animation, and is great for-
</p>
<ul>
<li>Object-oriented web applications</li>
<li>Encapsulating animation logic</li>
</ul>
<p>
This pen is a sample for <a target="_blank" href="https://www.youtube.com/watch?v=WaNoqBAp8NI">Modern Animation Fundamentals</a>, as part of the Google Developers channel on YouTube.
</p>
<div class="buttons">
<button id="playPause">Play / Pause</button>
</div>
</div>
CSS:
html {
min-height: 100%;
position: relative;
background: #263035;
}
body {
margin: 0;
font-family: 'Roboto', 'Arial', Sans-Serif;
min-height: 100%;
position: relative;
}
.ball-layer {
z-index: -1;
top: 0;
overflow: hidden;
position: fixed;
width: 100%;
}
.ball {
border-radius: 1000px;
background: #f44336;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
height: 64px;
width: 64px;
color: white;
margin: 12px 0;
}
.ball i {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
line-height: 64px !important;
font-size: 38px !important;
}
/** .dialog */
.dialog {
z-index: 10;
margin: 40px auto;
width: 400px;
background: #f7f7f7;
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);
overflow: hidden;
user-select: none;
color: #212121;
transition: all 2s ease-in-out;
}
.dialog:hover {
opacity: 0.8;
}
.dialog > h2 {
background: #673ab7;
margin: 0;
padding: 20px 24px;
color: white;
font-weight: 300;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.33);
}
.dialog p {
margin: 24px;
}
.dialog ul li {
margin: 4px;
}
.dialog button {
border: 0;
font: inherit;
background: 0;
margin-left: 8px;
padding: 8px 12px;
border-radius: 2px;
display: block;
text-transform: uppercase;
font-weight: 500;
float: right;
background: #ddd;
color: #888;
transition: all 0.25s;
cursor: pointer;
}
.dialog button:focus {
background: #b3e5fc;
color: #03a9f4;
outline: 0;
}
.dialog .buttons {
margin: 24px;
overflow: hidden;
}
JS:
// Imperative Animations, aka Web Animations.
// For more information-
// https://github.com/web-animations/web-animations-js
// https://github.com/web-animations/web-animations-demos
// https://github.com/web-animations/web-animations-codelabs
// Calculate a random number (0-30).
function randomPivot() {
var x = Math.random();
return x * x * 30;
}
// Generate a random Material-ish color, in the form of a string.
function randomMaterialColor() {
var v = Math.round(Math.random() * 160) + 200;
return 'hsl(' + v + ', 100%, 50%)';
}
// Use the following random icons from the Google Material Icons font. Generate one per icon.
var icons = 'cake star thumb_up favorite new_releases comment cast filter_hdr'.split(' ');
var iconEffects = icons.map(function(icon, i) {
var ball = document.createElement('div');
ball.className = 'ball';
ball.innerHTML = '<i class="material-icons">' + icon + '</i>';
layer.appendChild(ball);
var icon = ball.firstChild;
// Build iconKeyframeEffect, which sways the icon from side to side.
var amount = randomPivot();
var keyframes = [
{'transform': 'rotate(' + amount + 'deg)'},
{'transform': 'rotate(' + -amount + 'deg)'}
];
var timing = {
duration: 1000,
iterations: 1000,
iterationStart: 0.5,
direction: 'alternate'
};
var iconKeyframeEffect = new KeyframeEffect(icon, keyframes, timing);
// Translate from the left to right of the screen (using 64px => 80vw).
keyframes = [
{
'transform': 'translate(64px)',
'background': randomMaterialColor()
},
{
'transform': 'translate(80vw)',
'background': randomMaterialColor()
}
];
timing = {
easing: 'ease-in-out',
duration: (Math.random()*15 + 5) * 1000,
direction: 'alternate',
iterationStart: Math.random(),
iterations: 1000
};
var ballKeyframeEffect = new KeyframeEffect(ball, keyframes, timing);
// Combine these effects into a single group per ball.
return new GroupEffect([iconKeyframeEffect, ballKeyframeEffect]);
});
// Play one entire group containing all icon effects. Composition FTW!
var group = new GroupEffect(iconEffects);
var anim = document.timeline.play(group, {
duration: 10, // total duration
});
playPause.addEventListener('click', function() {
if (anim.playState == 'paused') {
anim.play();
} else {
anim.pause();
}
});
#10: Proton
粒子效果绝对是当下网页设计的趋势。设计师们不仅将它们用于背景效果,还在过渡场景中使用,甚至用于创意项目的演示。Proton 库专门针对快速缩放创意粒子效果的需求而定制。
通过该库可以实现诸如构建火花效果和基于碰撞的交互之类的操作,还可以将文本转换为动画。
示例代码:
HTML:
<script src="https://rawgit.com/phinajs/phina.js/v0.2.2/build/phina.min.js"></script>
<script src="https://rawgit.com/a-jie/Proton/v3.0.5/build/proton.min.js"></script>
<script src="https://rawgit.com/matsu7089/phina-proton/master/phina.proton.js"></script>
JS:
var SCREEN_W = 320;
var SCREEN_H = 320;
phina.globalize();
phina.define('MyEmitter', {
superClass: 'ProtonEmitter',
init: function() {
this.superInit({
rate: new Proton.Rate(Proton.getSpan(10, 20), 0.2),
initialize: [
new Proton.Radius(1, 10),
new Proton.Life(2, 4),
new Proton.Velocity(1, Proton.getSpan(0, 360), 'polar')
],
behaviour: [
new Proton.Alpha(1, 0),
new Proton.Color(['e91e63', '2196f3', 'ffeb3b']),
new Proton.CrossZone(new Proton.RectZone(0, 0, SCREEN_W, SCREEN_H), 'bound')
]
});
this.setPosition(SCREEN_W/2, SCREEN_H/3).emit();
var center = new Proton.Vector2D(SCREEN_W/2, SCREEN_H/2);
this.behaviours = [
new Proton.Attraction(center, 25, 150),
new Proton.Collision(this.protonEmitter),
new Proton.Force(30, 0),
new Proton.Gravity(1),
new Proton.GravityWell(center, 200),
new Proton.RandomDrift(20, 0, .035),
new Proton.Repulsion(center, 10, 150),
new Proton.Scale(1, 3)
];
this.index = 0;
this.addBehaviour(this.behaviours[this.index]);
},
changeBehaviour: function() {
this.removeBehaviour(this.behaviours[this.index]);
this.index = ++this.index % 8;
this.addBehaviour(this.behaviours[this.index]);
}
});
phina.define('MainScene', {
superClass: 'DisplayScene',
init: function(options) {
this.superInit(options);
this.backgroundColor = '#f0f0f0';
var protonLayer = ProtonLayer({
width: SCREEN_W,
height: SCREEN_H
}).setPosition(SCREEN_W/2, SCREEN_H/2).addChildTo(this);
var emitter = MyEmitter().addChildTo(protonLayer);
this.setInteractive(true);
this.on('pointstart', function() {
emitter.changeBehaviour();
});
if (phina.isMobile()) {
this.one('enterframe', function(e) {
var scene = phina.game.PauseScene(options);
scene.text.text = 'tap to start';
e.app.pushScene(scene);
});
}
}
});
phina.main(function() {
var app = GameApp({
startLabel: 'main',
width: SCREEN_W,
height: SCREEN_H,
fps: 60,
});
app.run();
});
总结
这些库都已经十分成熟了,查找示例也比较容易。不过没有最好的东西,每个库都有自己的优点和缺点。如果是我,我会选择GSAP,因为它经过大量优化,几乎可以实现任何我们能够想象的效果。但是,如果我们只是为了实现一个项目,那么像 Popmotion 这样的库就足够了。