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/translateYscale/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
效果