如何在网页上使用 GreenSock 制作动画

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

在网页上制作动画的方法真的数不胜数。 我们之前在这篇文章中介绍过 不同动画技术的比较。 今天,我们将深入探讨我最喜欢的实现方式之一的分步指南:使用 GreenSock.

(他们没有给我任何报酬,我只是真的很喜欢使用它们。)

为什么我更喜欢 GreenSock 而不是其他方法? 从技术上讲,它通常是完成任务的最佳工具。 即使对于复杂的运动,它的使用也极其直观。 以下是我更喜欢使用它的几个原因:

  • 您可以在 DOM 元素以及 WebGL/Canvas/Three.js 上下文中使用它们。
  • 缓动效果非常复杂。 CSS 动画在缓动方面仅限于两个贝塞尔曲线手柄,这意味着如果您想要一个弹跳效果,您将不得不为每个传递制作向上和向下,以及向上和向下的关键帧。 GreenSock 允许多个贝塞尔曲线手柄来创建高级效果。 弹跳只是一行代码。 您可以查看他们的 缓动效果可视化工具,了解我的意思。
  • 您可以在时间轴上排列运动。 当您必须同时协调多件事时,CSS 动画可能会变得有些混乱。 GreenSock 保持非常清晰,并允许您控制时间轴本身。 您甚至可以对动画进行动画处理!🤯
  • 它会在后台进行一些计算,以防止出现奇怪的跨浏览器行为,以及规范规定应为真的情况,例如堆叠变换的工作方式。
  • 它以插件的形式提供许多高级功能,如果您想进一步完善作品,可以使用这些功能,例如变形 SVG 形状、绘制 SVG 路径、拖放、惯性等等。

人们有时会问我,为什么我会选择这个特定的库,而不是其他所有选择。 它比大多数其他库都要先进——它们已经存在于 Flash 还是主流的时候了。 这个演示卷非常鼓舞人心,同时也说明了严肃的网页动画师确实会选择使用这个工具。

以下是有关如何在网页上创建运动的细分,我将其分解成尽可能小的单位。 我们开始吧!

对 DOM 元素进行动画处理

考虑一个用 <div> 制作的球体,它使用 border-radius 值为 50% 进行样式设置。 以下是使用 GreenSock 对其进行缩放和移动的方法

<div class="ball"></div>
gsap.to('.ball', {
  duration: 1,
  x: 200,
  scale: 2
})

在这种情况下,我们告诉 GreenSock (gsap) 对具有类 .ball 的元素进行 .to() 操作,以更改几个不同的属性。 我们将 transform: translateX(200px) 的 CSS 属性缩短为简化的 x: 200 (注意不需要单位,但可以以字符串的形式传递它们)。 我们也没有编写 transform: scale(2)。 以下是对您可能希望在动画中使用的变换以及其相应的 CSS 语法的参考

x: 100 // transform: translateX(100px)
y: 100 // transform: translateY(100px)
z: 100 // transform: translateZ(100px)
// you do not need the null transform hack or hardware acceleration, it comes baked in with
// force3d:true. If you want to unset this, force3d:false
scale: 2 // transform: scale(2)
scaleX: 2 // transform: scaleX(2)
scaleY: 2 // transform: scaleY(2)
scaleZ: 2 // transform: scaleZ(2)
skew: 15 // transform: skew(15deg)
skewX: 15 // transform: skewX(15deg)
skewY: 15 // transform: skewY(15deg)
rotation: 180 // transform: rotate(180deg)
rotationX: 180 // transform: rotateX(180deg)
rotationY: 180 // transform: rotateY(180deg)
rotationZ: 180 // transform: rotateZ(180deg)
perspective: 1000 // transform: perspective(1000px)
transformOrigin: '50% 50%' // transform-origin: 50% 50%

持续时间是您可能想到的:它是一秒钟的时间段。

那么,好的,我们如何对,比如说,SVG 进行动画处理呢? 让我们考虑上面相同的代码,但用 SVG 表示。

<svg viewBox="0 0 500 400">
  <circle class="ball" cx="80" cy="80" r="80" />
</svg>
gsap.to('.ball', {
  duration: 1,
  x: 200,
  scale: 2
})

从动画的角度来看,它实际上是 完全相同的。 它会获取具有类 .ball 的元素,并转换这些属性。 由于 SVG 实际上是 DOM 元素,我们可以对其中任何一个添加类,并以相同的方式对其进行动画处理!

太棒了!我们正在高效地进行操作。

缓动效果

我之前提到过,缓动效果是我最喜欢的功能之一,让我们看看如何使用它们。

让我们看看最初的球体。 也许我们想尝试一些更独特的弹跳缓动效果。 它会像这样

gsap.to('.ball', {
  duration: 1.5,
  x: 200,
  scale: 2,
  ease: 'bounce'
})

就是这样! GreenSock 的这个版本假设您想要使用 ease-out 时间函数(它更适合入场动画),因此它将其用作默认值。 您只需要将“bounce”指定为字符串,就可以开始使用它了。

您可能已经注意到我们也稍微延长了持续时间。 这是因为球体在初始状态和最终状态之间需要做更多的“工作”。 一秒钟的持续时间虽然对于线性或正弦缓动效果来说非常棒,但对于弹跳或弹性缓动效果来说,速度有点太快了。

延迟和时间轴

我提到过,默认的 ease-out 时间函数很适合入场动画。 那 ease-in 或 ease-in-out 出场动画呢? 让我们也来做一下。

gsap.to('.ball', {
  duration: 1.5,
  x: 200,
  scale: 2,
  ease: 'bounce'
})

gsap.to('.ball', {
  duration: 1.5,
  delay: 1.5,
  x: 0,
  scale: 1,
  ease: 'back.inOut(3)'
})

您可能已经注意到发生了一些事情。 例如,我们在倒数第二行 (ease: 'back.inOut(3)') 没有使用 bounce.in。 相反,我们使用了另一种称为 back 的缓动效果,用于 ease-in-out。 我们还传递了一个配置选项,因为如您从 Greensock 的缓动效果可视化工具中看到的那样,我们不仅仅限于该缓动效果的默认配置。 我们可以根据自己的需要对其进行调整。 太棒了!

您可能还注意到,我们使用延迟将动画链接在一起。 我们获取了第一个动画的持续时间,并确保下一个动画的延迟与之匹配。 现在,这在这里起作用,但这非常脆弱。 如果我们想更改第一个动画的长度呢? 那么我们必须返回并更改后面的延迟。 如果我们之后还有另一个动画怎么办? 以及另一个动画? 好吧,我们必须返回并计算所有后续延迟。 这需要大量的手动工作。

我们可以将这项工作转交给计算机。 我的一些更复杂的动画包含数百个链接在一起的动画! 如果我完成工作并想要调整开头的内容,我不希望必须返回并修改所有内容。 这就是时间轴的作用

gsap
  .timeline()
  .to('.ball', {
    duration: 1.5,
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    duration: 1.5,
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

这会实例化一个时间轴,然后将这两个动画链接到它。

但是我们仍然有一些重复,我们在每个动画中都使用了相同的持续时间。 让我们创建一个默认值,作为传递给时间轴的选项。

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

太酷了! 好吧,您可能已经开始了解以这种方式构建事物的方式了。 虽然在这样一个简单的动画中可能没什么大不了的,但在真正复杂的动画中,默认值和时间轴可以真正保持代码的可维护性。

现在,如果我们想在球体的另一个方向上镜像此运动,并且只是……让它继续下去怎么办? 换句话说,如果我们想让它循环播放怎么办? 这时我们就添加 repeat: -1,它可以应用于单个动画或整个时间轴。

gsap
  .timeline({
    repeat: -1,
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  })
  .to('.ball', {
    x: -200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

我们不仅可以使其重复播放,还可以使其来回重复播放,就像一个弹簧玩具一样。 这就是我们为什么将其称为 yoyo: true 的原因。 为了说明这一点,我们将只显示第一个动画。 您可以看到它会向前播放,然后会反向播放。

gsap
  .timeline({
    repeat: -1,
    yoyo: true,
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })

重叠和标签

我们可以轻松地将动画链接在一起,这很棒,但现实生活中的运动并不完全是这样。 如果您穿过房间去拿一杯水,您不会走。 然后停下来。 然后拿起水。 然后喝下去。 您更有可能以一种连续的运动来完成这些动作。 因此,让我们简要讨论一下如何重叠运动,以及如何使事物同时发生。

如果我们想确保事物在时间轴上的特定时间点之前和之后发生,我们可以使用增量器或减量器。 如果我们以以下示例为例,它显示三个球体一个接一个地进行动画处理,那么它会显得有点僵硬。

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })

如果我们使用作为字符串传递的减量器略微重叠运动,那么事物会变得更加流畅。

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, '-=1')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, '-=1')

我们还可以使用一种称为标签的东西来实现这一点。 标签确保事物在动画播放头的特定时间点开始。 它看起来像这样:.add('labelName')

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .add('start')
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start') 

我们甚至可以从标签中增加和减少。 我实际上在我的动画中经常这样做。 它看起来像这样'start+=0.25'

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .add('start')
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start+=0.25')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start+=0.5') 
 

哇! 我们可以用它做这么多事情! 以下是一个动画示例,它将许多这些前提组合在一起,并使用纯 JavaScript 进行了一些交互。 请务必点击铃铛。

如果您正在寻找更多使用 GreenSock 进行的框架驱动的动画,这里有一篇 我写过的介绍 Vue 5 动画的文章,以及一篇 我发表的演讲,它介绍了 React——这篇文章已经有一两年了,但基本前提仍然适用。

但是,还有很多我们没有涵盖的内容,包括交错、SVG 形变、绘制 SVG、在屏幕上移动物体、沿着路径移动物体、动画文本…… 你可以随意提!我建议您前往 GreenSock 文档 查看这些内容的详细信息。我还有一门 Frontend Masters 的课程 更深入地介绍了所有这些内容,并且课程资料 在我的 GitHub 上开源。我还有一些 公开的 Pen 代码供您分叉和使用。

希望这能帮助您开始在网络上进行动画制作!我迫不及待地想看看您会做出什么作品!