大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

我们在网上浏览时,经常能看到很多网站,随着页面滚动条向下滑动时,有非常丰富的页面动画效果;相信很多小伙伴也都很好奇,这样的网站效果是如何做出来的。我们本文就来深入的学习一下GSAP这个库的用法,为后面实现酷炫的动画效果打下基础。

首先这样的滚动效果和fullpage.js、Swiper.js全屏翻页滚动轮播的效果是不一样的,页面元素的位置极度的依赖于滚动条的位置,因此是需要监听滚动条事件;笔者在调研了better-scroll.js、scrollReveal.js和iScroll.js等一系列插件后,发现这些插件并不能满足需求。

笔者也曾一度想过不依赖库,自己来实现类似的效果,不就是监听页面滚动么;但是想了想滚动时这么多元素的动画效果导致的性能问题以及页面resize后如何来重新计算也是不小的问题,于是就打消了不切实际的念头。

在扒开很多网站的源代码之后,笔者找到了一个很多网站都在用的动画库:GSAP;但是很奇怪,网站搜索这个库,我们发现它的教程非常的少,这么好用的一个动画库不应该资料这么匮乏;但是看到官网全英文的教程和有时候无法访问demo教程后,以及有点难理解的各种概念后,我好像知道了原因。

由于我们要实现的很多动画效果都依赖于GSAP,因此我们先来看下GSAP的使用教程。

GSAP

首先我们要知道这个库能做什么,The GreenSock Animation Platform (GSAP)是一个功能十分强大的动画平台,可以帮助我们实现大部分的动画需求,构建高性能的、适用于所有主要浏览器的高性能动画;GSAP非常的灵活,可以在任何框架上处理页面能够所有通过js改变的元素,不仅可以对div的css属性进行动画,还是SVG、React、Vue、WebGL,甚至和Threejs一起使用。

除了GSAP核心库外,还有很多实用的插件,比如结合ScrollTrigger插件,我们可以实现非常震撼的滚动触发效果;同时也不需要担心响应式的问题,GSAP确保项目响应迅速、高效且流畅。

动画属性

我们从一个简单的例子开始,先把一个.box元素沿着X轴移动200px;

gsap.to('.box', { x: 200 });
3abc44ed5c21971eb2867fd4dba9c320.gif
demo1

如果我们对.box元素进行元素检查,我们会发现GSAP实际上是不停的修改transform属性,直至最终停留在transform: translate(200px, 0px);我们继续回到上面的代码。

92b8141df3ba1dea1e35c61932d58c3e.png
gsap.to

在上段代码中,我们发现这段代码包含有3层含义:函数、目标和变量;首先目标就是我们想要移动的元素,可以是CSS选择器,也可以使dom元素,甚至是一串数组:

// CSS选择器
gsap.to(".box", { x: 200 });
gsap.to("#box1", { x: 200 });

// dom元素
const box = document.querySelector(".box");
gsap.to(box, { x: 200 })

// 元素数组
let square = document.querySelector(".square");
let circle = document.querySelector(".circle");
                                      
gsap.to([square, circle], { x: 200 })

然后是函数,有四种类型的动画函数:

  1. gsap.to:最常用的动画类型,从当前状态开始。

  2. gsap.from:和.to相反,从一个状态开始到当前状态。

  3. gsap.fromTo:可以自定义开始和结束状态。

  4. gsap.set:立即设置属性,没有动画效果。

我们直接看效果就能明白这几个函数的意义了。

gsap.from(".box", {
  x: 300,
});
a454c6a59017e4bc25c437a2de22574d.gif
demo3
gsap.fromTo(
  ".box",
  {
    x: 0,
    y: 0,
  },
  {
    x: 400,
    y: 50,
  },
);
a676907db8dbbc495f8f7195fe763618.gif
demo4
gsap.set(".box", {
  x: 400,
  y: 50,
});
cb844d3f56b4510e3b69d8f52cad0580.gif
demo5

最后是变量对象,这个对象可以包含的信息种类就比较丰富了,可以是想要动画的任意CSS属性,也可以是影响动画表现形式的特殊属性,比如duration持续时间、repeat重复次数。

gsap.to(target, {
  x: 400,
  y: 50,
  rotation: 180,
  // 特殊属性
  duration: 3,
  repeat: 2
});
e9be7ac71ff1f8166637b482755c5681.gif
demo6

GSAP可以动画任何属性,没有确定的列表,包括CSS属性、自定义对象属性甚至CSS变量和复杂的字符串,最常见的动画属性是transforms和透明度。transforms属性是动画中性能消耗最小的,可以用它来移动元素、旋转或者放大缩小,因为他们不会影响页面的布局,更不会使页面重排,因此有着较好的性能表现。

尽可能的使用transforms,而不是布局属性,例如top、left或者margin,有更平滑的动画体验。

我们可能比较熟悉以下的transforms属性:

transform: rotate(360deg) translateX(10px) translateY(50%);

GSAP提供了下面的缩写形式,上面的transforms属性可以直接缩写成下面的属性(yPercent表示百分比元素的高度):

{ rotation: 360, x: 10, yPercent: 50 }

GSAP支持CSS属性转为小驼峰形式,例如background-color变成backgroundColor

通过上面的例子我们也发现了,默认情况下GSAP会给transform属性使用px和degrees单位,比如{x: 10, rotation: 360}就表示x轴10px,旋转360度;但是我们有时候想要使用其他的单位,比如vw,radians或者相对单位。

x: 200, // 默认px
x: "+=200" // 相对值
x: '40vw', // 视窗单位
x: () => window.innerWidth / 2, // 函数计算
  
rotation: 360 // 默认角度
rotation: "1.25rad" // 使用弧度单位

GSAP的神奇之处在于,不仅能够对dom元素动画,还能够对非dom元素,比如svg、js对象等进行动画操作;对于svg元素,我们添加attr属性额外的处理一些svg的属性,像width、height、fill、stroke、opacity等。

203909374b8345349642e20cd87f2a03.gif
demo8
gsap.to(".svgBox", {
  duration: 2,
  x: 100,
  xPercent: -100,
  // svg属性
  attr: {
    fill: "#8d3dae",
    rx: 50,
  },
});

甚至,我们对js对象进行动画时,不需要任何dom元素,针对任意js对象的任意属性进行动画,onUpdate函数用于监听动画的更新过程:

let obj = { myNum: 10, myColor: "red" };
gsap.to(obj, {
  myNum: 200,
  myColor: "blue",
  onUpdate: () => console.log(obj.myNum, obj.myColor)
});

特殊属性

特殊属性用来调整动画的表现形式,我们在上面用到了repeat和duration,下面的文档中提供了一些常用的属性:

属性名描述
duration动画的持续时间(单位:秒)默认0.5秒
delay动画重复次数
yoyo布尔值,如果为true,每次其他动画就会往相反方向运动(像yoyo球)默认false
stagger每个目标动画开始之间的时间(秒)
ease控制动画期间的变化率,默认"power1.out"
onComplete动画完成时的回调函数

repeat属性就是重复的次数,会让动画执行多次;需要注意的是,如果我们填一个数值2,但实际动画的次数是3,因此我们总结出来公式:真实运动次数 = repeat属性 + 1

如果我们想让动画一直重复下去,使用repeat: -1

repeat一般会和yoyo属性一起使用,当yoyo为true时,在每次动画结束都会反向运动;需要注意的是,一个运动循环包含一个正向和反正运动,反向运动也计入运动的次数中。

gsap.to(".box", {
  rotation: 360,
  x: 300,
  xPercent: -100,
  duration: 2,
  repeat: 2,
  yoyo: true,
});

我们这边repeat写的2,实际动画中,正好是3次运动,1.5次循环往复运动。

delay也非常好理解,动画开始延迟时间,如果后面是repeat重复的动画,则不会有延迟了;如果我们想要为后面的任何重复运动添加延迟,可以使用repeatDelay属性。

gsap.to(".green", {
  rotation: 360,
  duration: 1,
  delay: 1,
  repeat: 1,
});
gsap.to(".purple", {
  rotation: 360,
  duration: 1,
  repeat: 1,
  repeatDelay: 1,
});
2337994446f7617e904f019082e7405b.gif
demo11

我们发现同样是总计2次的重复旋转运动,绿的div动画开始前有停顿,而后面的重复运动就没有停顿了;而紫色的div动画开始前没有停顿,在后面的每次重复运动则会有停顿,就是repeatDelay的作用。

ease速度曲线也是动画效果的一部分,我们可以看到不同的速度曲线旋转效果也是不一样的。

gsap.to(".green", {
  rotation: 360,
  duration: 2,
  ease: "none",
});
gsap.to(".purple", {
  rotation: 360,
  duration: 2,
  ease: "bounce.out",
});

stagger属性也是比较有趣的属性,我们可以利用它控制多个目标之间动画的延迟差,形成奇妙又好看的交错效果。

gsap.to(".box", {
  duration: 0.5,
  opacity: 0,
  y: -100,
  stagger: 0.1,
  ease: "back.in",
});
fab5a2e33328ece65ed90b13ddef5faf.gif
demo13

比如这样,让div交错消失的场景;或者交错动画一个阵列,只需要告诉GSAP有多少行列。

gsap.to(".box", {
  scale: 0.1,
  y: 30,
  yoyo: true,
  repeat: -1,
  ease: "power1.inOut",
  delay: 1,
  stagger: {
    amount: 1.5,
    grid: "auto",
    from: "center",
  },
});
ae27fbc0cf5bfa024e924ce5c8b0cb40.gif
demo14

时间线timeline

我们动画经常会遇到多个对象的情况,虽然我们可以使用上面的delay进行简单的控制,延迟物体的动画开始时间;但是如果中间某个物体的动画执行时间突然延长了,那么其后面所有的动画时间需要进行手动进行延迟,这显得非常不方便;因此我们需要引入时间线timeline的概念。

时间线是GSAP最重要的概念之一

我们通过gsap.timeline()创建一个时间线,然后通过时间线控制每一个动画顺序执行;这样即使我们修改中间某个动画的duration,也不会影响后续时间线。

const t1 = gsap.timeline();
t1.to(".green", {
  x: 600,
  duration: 2,
});
t1.to(".purple", {
  x: 600,
  duration: 1,
});
t1.to(".orange", {
  x: 600,
  duration: 1,
});
46db6e4c893adccec5e6e0650b78e0d6.gif
demo15

但是如果我们想要在一个动画开始的同时,执行另一个动画,除了再额外创建一条时间线,我们可以在to函数后面加一些小参数来进行精确的控制。

const t1 = gsap.timeline();
t1.to(".red", { x: 400,duration: 1 });
// 在1秒开始插入动画(绝对值)
t1.to(".green", { x: 400, duration: 1 }, 1);
// 在上个动画的开始插入动画
t1.to(".purple", { x: 400, duration: 1 }, "<");
// 在最后一个动画结束后一秒插入动画
t1.to(".orange", { x: 400, duration: 1 }, "+=1");
04193f5f8c0503815fddc7e11da4c8df.gif
demo16

理解上面代码中的这些小参数可以帮助我们构建很多复杂精妙的动画效果,让我们能够在任意时间点来执行任意的动画效果;上面的例子乍一看可能不是那么好理解,不过没有关系,我们一点点来理解。

虽然我们上面都是以gsap.to来为例,但是其他的函数比如from()、fromTo()、add()等也都适用;需要注意的是这些参数跟在变量对象的后面,因此函数的代码结构如下:

.method( target, vars, position )

我们将这些参数简单的分一下类就好理解多了,其实主要有以下几种类型:

  • 绝对值:在某个绝对秒数来执行动画。

  • <符>符:"<"在上个动画开始,">"在上个动画结束。

  • 相对符:+=在最后一个动画结束后,-=在最后一个动画结束前。

  • label值:直接用某个时间点的label名。

绝对值就表示在某个绝对的秒数时执行动画,比如上面demo中的green元素,在1秒时执行动画;<符号表示在上个动画开始,比如demo中的purple元素,就和green元素同时执行动画;我们还可以在后面加个数值,比如:<3和<=3,两种表达方式的含义相同,都表示在上个动画开始后的三秒执行。

const t1 = gsap.timeline();
t1.to(".green", { x: 400, duration: 1 })
  .to(".purple", { x: 400, duration: 1 }, "<3")
  .to(".orange", { x: 400, duration: 1 }, ">1");
7aab296e381aa402647ce7bb63515c63.gif
demo17

在上面的gif效果中,我们看到,purple元素是在green元素开始的3秒后才开始执行,并不是结束的3秒后;>符号则表示上个动画结束时,用法类似,这里就不再赘述了。

相对符则表示动画结束的时间点,+=1表示上个动画结束1秒后,-=2表示上个动画结束前2秒。

label值则很好理解了,在某个时间点插入一个label,在这个label前面或者后面的时间来执行,我们看下它的用法:

const t1 = gsap.timeline();
t1.to(".green", { x: 400, duration: 1 })
  .add("myLabel", 2)
  .to(".purple", { x: 400, duration: 1 }, "myLabel+=1")
  .to(".orange",{ x: 400, duration: 1 }, "myLabel-=1");

通过gsap.add函数,我们在2秒处放置了一个myLabel的标识,在后面使用myLabel+=1和myLabel-=1相对这个标识的时间进行控制。

020e4b514867620711a97acf0adb644f.gif
demo18

不同时间线中的动画可能会有相同的特殊属性,比如repeat和delay等,我们可以在时间线的创建函数中统一设置,避免重复:

const tl = gsap.timeline({ repeat: 1, repeatDelay: 1, yoyo: true });
tl.to(".green", { rotation: 360 })
  .to(".purple", { rotation: 360 })
  .to(".orange", { rotation: 360 });

如果你发现某个属性你重复使用了很多次,比如x、scale、duration等,我们就可以使用defaults属性,任何加到defaults属性中的参数都会被下面的函数继承。

const tl = gsap.timeline({
  defaults: {
    scale: 1.2,
    duration: 2,
  },
});
tl.to(".green", {
  x: 200,
})
  .to(".purple", {
    x: 400,
  })
  .to(".orange", {
    x: 600,
  });

回调函数

在有些情况下,我们需要对动画的开始、过程、结束的某个时间点进行回调操作,gsap提供了以下回调函数:

  • onComplete:动画完成时。

  • onStart:动画开始时

  • onUpdate:动画更新时。

  • onRepeat:动画重复时。

  • onReverseComplete:当动画在反转到达开始时。

gsap.to(".class", {
  x: 100, 
  onComplete: () => console.log("the tween is complete")
}

// 时间线所有动画结束时调用
gsap.timeline({onComplete: tlComplete});

function tlComplete() {
  console.log("the tl is complete");
  // ...
}

ScrollTrigger

现在我们对gsap的基本用法有了一定的了解,下面我们来看下插件的用法;插件可以帮助我们扩展动画的高级功能,让动画的表现更丰富;我们主要来了解ScrollTrigger的使用。

我们先看下ScrollTrigger的一个简单用法,

import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

gsap.to(".green", {
  rotation: 360,
  scale: 1.5,
  backgroundColor: "red",
  scrollTrigger: {
    trigger: ".green",
    scrub: true,
  },
});
gsap.to(".purple", {
  rotation: 360,
  scale: 1.5,
  backgroundColor: "red",
  scrollTrigger: {
    trigger: ".purple",
    scrub: 1,
  },
});

使用前当然要对插件进行注册了,使用gsap.registerPlugin将ScrollTrigger注册,否则我们在下面操作时会发现没有任何效果。

在to函数中我们新增了一个scrollTrigger属性,trigger表示当前动画触发的元素,这个很好理解,我们使用当前元素;markers是否进行标记,scrub表示是否将动画效果链接到滚动条,随着滚动条平滑处理;如果是false(默认),随着元素出现在视窗内,直接触发动画,如果是true,则平滑动画,我们看下效果:

scrub还可以是某个具体的数值,表示延迟滚动条多少秒动画;比如这里的1,延迟1秒执行动画。

我们在滚动浏览器时,可以使用pin属性将某个元素固定在某个位置;pin可以是css选择器字符串、布尔值或者直接dom元素;如果是true,则直接固定当前的动画元素;我们这里使用pin将purple元素固定起始位置:

gsap.to(".green", {
  x: 400,
  duration: 2,
  scrollTrigger: {
    trigger: ".green",
    pin: ".purple",
  },
});

start和end

start和end属性用来决定滚动触发元素开始的位置,可以是字符串、数值或者函数,两者的用法类似,我们以start为例;start的值默认是"top bottom",它的含义是当触发物体(trigger)的顶部(top)碰到浏览器的底部(bottom)时;我们看下当开启标记marker时的触发位置。

d6c2b290c69261c5cc6d811bc1acb791.png
start和end默认触发位置

我们看到scroller-start的线就是浏览器视窗的边界线,当浏览器向下滚动时,这条线滚动到物体的start线时,就触发了动画效果;同样的道理,向上滚动时,当scroll-end的线触碰到end时,动画结束。

start值看起来很怪异,不好理解,其实我们可以把它拆成两部分来看;第一个top值表示物体的上边界,同样的我们可以设为bottom(物体下边界)、center(物体中间)或者具体数值(100px、80%),即控制的是物体旁边的start线。

第二个值表示浏览器视窗滚动触发的scroller-start线,bottom表示视窗的底部,我们也设为top或者center或者数值,以及百分比(例如80%,表示整个视窗的80%高度),甚至是相对位置,比如bottom-=100px

toggleClass

有些情况下,我们不想要gsap的动画,而是想用我们自己自定义的css类名来实现某些动画效果,toggleClass属性可以让我们在触发的元素上添加或者移除这些的类名,从它的名字也能看出来它是处理类名的;它可以是一个字符串,例如toggleClass: "active",就表示要新增/移除的类名。

toggleClass也可以是对象,可以在其他的元素上来新增/移除类名,比如:

toggleClass: {targets: ".my-selector", className: "active"}

我们可以将ScrollTrigger结合timeline创建动画。

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: ".wrap",
    scrub: true,
  },
});
tl.to(".green", {
  x: 200,
});
tl.to(".purple", {
  x: 400,
});

总结

本文GSAP所有的用法教程大致到这里就结束了,本文涉及到了一些动画方面的概念,有些不准确的地方欢迎指正。

Node 社群



我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

   “分享、点赞、在看” 支持一波👍
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐