当下10大流行的JavaScript动画库,看一看有你喜欢的吗?

3,649 阅读12分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情

当下10大流行的JavaScript动画库,看一看有你喜欢的吗?

我喜欢去尝试各种动画效果,尤其是使用CSS实现简单的动画。但是,如果从长远来看,只会使用CSS实现动画让我们走不远。而且,若你想实现复杂的交互式用户体验,你早晚要用到 JavaScript。

使用 JavaScript实现动画效果的主要优势在于你能够控制更多的动画逻辑,包括动画转换的流动性、控制DOM的状态和响应以及使用WebGL实现2D和3D图形。

JS动画库有许多类型,它确实有助于帮助缩小我们关注的领域。并且很多引擎和框架不仅仅用于前端,同时还用于游戏和其他交互式内容。所以需要注意,我们该篇文章选择的JS动画库是前端开发中常用的库。

#1: Three.js

image.png

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();

three.js.gif

#2: Anime.js

img

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'
 });

仅使用上面的代码片段,就可以实现如下所示的效果:

img

#3: Popmotion

img

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

img

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();
 }

mo.js.gif

#5: p5.js

img

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++
     }
   }
 }

p5.js.gif

#6: Motion

img

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"));

motion.js.gif

#7: GSAP

img

该库不仅针对性能进行了优化,而且兼容性很好——兼容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();

GASP.js.gif

#8: Paper.js

img

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]);
 }

Paper.js.gif

#9: Web Animations

img

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();
   }
 });
 ​
 ​

image.png

#10: Proton

img

粒子效果绝对是当下网页设计的趋势。设计师们不仅将它们用于背景效果,还在过渡场景中使用,甚至用于创意项目的演示。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 这样的库就足够了。

翻译来源:stackdiary.com/javascript-…

作者: Alex Ivanovs