用gsap构造简单的动画

278 阅读4分钟

gsap的能力

本质是数据插值库
给定起点、终点、duration和变化规则 gsap会自动计算中间态
理论上可以计算任意属性在任意维度的变化情况
gsap可以和pixi.js、three.js等配合使用 但这里仅讨论简单的dom动画

gsap.to

这是gsap中最常用的函数 以当前位置为起点 指定终点和过渡方式开启动画
多个gsap.to可以同时使用 会叠加
gsap.to(el,options)
el是gsap操纵的对象 可以是一个css选择器 也可以是一个dom对象
options可以填入任意css属性 包括left,color,opacity等
数值类型的单位默认是px 但也可以设置字符串值自定义单位 例如'50vw'
此外gsap还对transform属性做了简化:

  • x/y->translateX/translateY
  • scale/scaleX/scaleY->同名transform函数
  • rotation->rotate 它的X/Y/Z版本也有相同映射相同
  • skew同上
  • transformOrigin->transform-origin

除了css属性 options内有一些"特殊属性" 对gsap有特殊含义 包括但不限于:

  • duration 动画时间(秒)
  • ease 渐变方式 具体参考下文
  • paused 是否暂停
  • keyframes 以关键帧数组的方式构造动画
  • onComplete,onUpdate等事件函数

还有动画重复、逆向等设置 更多内容参考文档

Tween

gsap.to的返回值是一个tween
这是一个promise like对象 可以await 用于指示动画结束的时点
tween拥有开启、结束、暂停动画的函数,也可以读取和设置属性等
例如 tween.pause可以暂停动画 tween.duration()会获取duration
而读取值的函数也可以设置值 tween.duration(5)会设置duration
具体参考文档

Ease

options中的ease属性 用于描述速度渐变情况(慢入快出,匀速移动等)
和css中transition-function作用相同
gsap预设了几套不同"力度"的速度方案 也可以自定义ease属性
自定义属性使用CustomEase类 需要传入一个SVG路径
CustomEase还派生出若干细分领域的类 参考文档

其他函数

gsap.from

以当前位置为终点 指定起点开始动画
返回值是tween 与gsap.to相同

gsap.fromTo

手动指定起点和终点
返回值是tween 与gsap.to相同

gsap.set

立刻设置属性

更多函数参考文档

时间线

时间线用于统一管理一组动画

const tl = gsap.timeline();

tl.to(xx)
tl.to(xx)

构造时间线时 可以指定为所有附加动画设置默认值

const tl = gsap.timeline({ defaults:{ duration: 5 } });

还可以使用嵌套的时间线

const tl1 = gsap.timeline()
const tl2 = gsap.timeline()

tl1.add(tl2)

为时间线附加动画或者嵌套时间线时 可以传入一个额外参数label 用于控制动画的并行情况

const tl = gsap.timeline();

tl.to(el,options,3) // 时间线启动3秒后开始此动画

label的取值和含义如下

  • 数字(n) 时间线启动n秒后开启此动画
  • < 与上一个动画同时启动
  • <+n 上一个动画启动后n秒再启动 <-n表示n秒之前
  • > 在上一个动画结束时启动
  • >+n 在上一个动画结束后n秒再启动 >-n表示n秒之前

更多规则参考文档

清理函数

tween和timeline具有revert函数 可以释放占有的资源 避免内存泄漏
也可以使用gsap.context()创建上下文 同意释放资源

const ctx = gsap.context(() => {
  gsap.to(".box", { x: 200 });
});

// 需要清理时
ctx.revert();

常见插件

插件可以拓展gsap的能力 让其不止基于时间流进行插值计算
这里仅列出常见插件 更多参考文档

ScrollTrigger

按照滚动情况展示动画

ScrollSmoother

让滚动更加平滑

MotionPath

指定dom的运动轨迹.
可以构造一条经过指定点的曲线作为路径 也可以使用svg path.
在路径上运动时 会自动旋转元素 让其法向量与路径垂直

SplitText

炫酷的文字特效

插件的原理

创建一个初始暂停的tween 插件会通过tween.progress函数控制动画进度

示例:用gsap构造抛物线

x方向匀速运动
y方向用贝塞尔曲线模拟上抛
两种动画复合

'use client'

import { FC, useEffect, useRef } from 'react'
import { Button } from 'antd'
import gsap from 'gsap'
import { CustomEase } from 'gsap/CustomEase'
import { useImmer } from 'use-immer'

const Page: FC = () => {
  const [path, setPath] = useImmer<string[]>([])
  const startAnime = useRef<() => void>()
  useEffect(() => {
    // 注册插件
    gsap.registerPlugin(CustomEase)
    const ball = document.querySelector('#ball') as HTMLDivElement
    const tl = gsap.timeline({
      defaults: { duration: 1 },
      paused: true,
      onUpdate: () => {
        const val = ball.style.transform
        setPath((draft) => {
          if (draft.at(-1) !== val) {
            draft.push(ball.style.transform)
          }
        })
      },
    })
    tl.to(ball, {
      x: '5rem',
      ease: 'none',
    })
    tl.to(
      ball,
      {
        y: '-5rem',
        ease: CustomEase.create('custom', 'M0,0 C0.44,1.58,0.80,1.29,1,1'),
      },
      '<',
    )
    startAnime.current = () => {
      tl.restart()
      setPath([])
    }
    return () => tl.revert()
  }, [setPath])
  return (
    <>
      <Button type='primary' onClick={() => startAnime.current?.()}>
        start
      </Button>
      <div className='w-40 h-40 bg-black relative'>
        <div
          id='ball'
          className='w-2 h-2 rounded-full bg-red-500 absolute left-0 bottom-0'
        ></div>
        <div className='w-full h-full'>
          {path.map((p) => {
            return (
              <div
                key={p}
                className='w-0.5 h-0.5 rounded-full bg-green-400 absolute left-0 bottom-0'
                style={{ transform: p }}
              ></div>
            )
          })}
        </div>
      </div>
    </>
  )
}

export default Page

效果
动画.gif