G1:Tween & Timeline 核心——GSAP 动画的基石
GSAP 动画实战系列 系列总览 | G1 Tween & Timeline 核心 ← 本文 | G2 ScrollTrigger | G3 文本动画 | G4 SVG 动画 | G5 GSAP + React | G6 AI 项目实战 | G7 社区案例
1. Tween——GSAP 的基本单元
Tween(补间)是 GSAP 的最小动画单元。它的作用是把属性从 A 状态平滑过渡到 B 状态。
1.1 三种创建方式
// gsap.to():从当前位置动画到目标值
gsap.to('.box', { x: 200, duration: 1 })
// gsap.from():从指定值动画到当前位置
gsap.from('.box', { x: 200, duration: 1 })
// gsap.fromTo():完全控制起点和终点
gsap.fromTo('.box',
{ x: -100, opacity: 0 },
{ x: 200, opacity: 1, duration: 1 }
)
什么时候用哪个?
| 方法 | 使用场景 |
|---|---|
to |
元素已经在 DOM 里有了位置,你想让它移走 |
from |
元素渲染后你想让它"从某个状态飘入"(最常见) |
fromTo |
你需要完全控制起止值,不依赖元素当前状态 |
gsap.from() 是日常开发中使用频率最高的——页面加载时让元素淡入、滑入、缩放入。
1.2 可动画的属性
GSAP 能动画化几乎所有东西:
gsap.to('.box', {
// CSS Transform(GSAP 的快捷属性,性能最优)
x: 100, // translateX
y: 50, // translateY
scale: 1.5, // scale
rotation: 360, // rotate(角度)
skewX: 30,
// CSS 属性
opacity: 0.5,
width: '300px',
backgroundColor: '#ff0000',
// SVG 属性(见 G4)
attr: { cx: 100, cy: 50 },
// 自定义对象属性
count: 100,
onUpdate() {
document.querySelector('.counter').textContent = Math.round(this.targets()[0].count)
}
})
重要提示:尽量用 x / y 而不是 left / top。GSAP 的 transform 快捷属性走 GPU 合成层,不触发 layout 重排。left / top 会触发 layout,在滚动动画中尤其致命。
1.3 动画控制
const tween = gsap.to('.box', { x: 500, duration: 2 })
tween.pause() // 暂停
tween.resume() // 恢复
tween.play() // 从头播放
tween.reverse() // 反向播放
tween.seek(1) // 跳到 1 秒位置
tween.progress(0.5) // 跳到 50%
tween.timeScale(2) // 2 倍速
tween.kill() // 销毁
tween.restart() // 重新开始
这种可编程的播放控制是 CSS Animation 做不到的。想象一个交互式的数据可视化面板——用户拖动滑块时后端数据折线图动态重绘,暂停/恢复/跳转都是基本需求。
2. Timeline——动画编排器
当一个 Tween 不够用时,Timeline 登场了。Timeline 是动画的容器,按时间轴编排多个 Tween。
2.1 基础用法
const tl = gsap.timeline({ defaults: { duration: 0.6, ease: 'power2.out' } })
tl.to('.header', { y: 0, opacity: 1 })
.to('.content', { y: 0, opacity: 1 })
.to('.footer', { y: 0, opacity: 1 })
这个例子中,.header 先出场,0.6s 后 .content 出场,0.6s 后再 .footer。默认情况下,每个 Tween 都在上一个结束后才开始。
2.2 位置参数——精确控制时序
这是 Timeline 最强大的特性:
const tl = gsap.timeline()
tl.to('.a', { x: 100, duration: 2 })
.to('.b', { x: 100, duration: 1 }, '-=1') // b 在 a 结束前 1s 开始
.to('.c', { x: 100, duration: 1 }, '+=0.5') // c 在上一个结束 0.5s 后开始
.to('.d', { x: 100, duration: 1 }, '>') // d 紧跟上一个之后(默认)
.to('.e', { x: 100, duration: 1 }, '<') // e 和上一个同时开始
.to('.f', { x: 100, duration: 1 }, '-=50%') // f 在上一个重叠 50% 处开始
.to('.g', { x: 100, duration: 1 }, 'a+=0.2') // g 在标签 "a" 后 0.2s 开始
位置参数语法:
| 参数 | 含义 |
|---|---|
0 或空 |
在 last tween 终点 |
1 |
在 1 秒处(绝对时间) |
'+=0.5' |
在 last tween 终点 + 0.5s |
'-=1' |
在 last tween 终点 - 1s(重叠) |
'<' |
和上一个同时开始 |
'>' |
紧跟上一个之后 |
'label+=0.2' |
从指定标签偏移 |
2.3 Labels——给时间轴打标签
标签让你在复杂 Timeline 中定位不再靠心算:
const tl = gsap.timeline()
tl.addLabel('intro')
.to('.hero', { opacity: 1, duration: 1 })
.addLabel('content-start')
.to('.card1', { y: 0, opacity: 1, duration: 0.5 })
.to('.card2', { y: 0, opacity: 1, duration: 0.5 })
.to('.card3', { y: 0, opacity: 1, duration: 0.5 })
.addLabel('content-done')
.to('.cta', { scale: 1.1, duration: 0.3 })
// 跳到内容区域
tl.seek('content-start')
// 从特定标签处播放
tl.play('intro')
这在分段交互中特别实用——比如一个教程引导流程,用户可以点击"上一节""下一节"来导航到不同的标签位置。
2.4 Defaults——减少重复
const tl = gsap.timeline({
defaults: {
duration: 0.5,
ease: 'power3.out',
stagger: 0.1
}
})
// 后续所有 Tween 自动继承这些默认值
// 不需要每个都重复写
tl.to('.card', { y: 0, opacity: 1 })
.to('.title', { x: 0, opacity: 1 }, '-=0.2')
单个 Tween 也可以覆盖默认值:
tl.to('.slow-card', { duration: 1.5, y: 0, opacity: 1 })
3. Eases——缓动函数
📊 在线查看所有 Easing 曲线可视化 | 也可用 Cubic-Bezier.com 调自定义曲线
GSAP 内置了 power1-4、back、bounce、elastic、circ、expo、sine 等多个系列,每个都有 .in / .out / .inOut 三种变体
Ease 决定了动画的"性格"——是干脆利落,还是弹跳有趣。
3.1 基础 Eases
gsap.to('.box', { x: 300, duration: 2, ease: 'power2.out' })
GSAP 内置了这些 ease:
| Ease | 感觉 | 适合场景 |
|---|---|---|
'none' |
匀速 | 加载进度条、旋转 |
'power1.inOut' |
柔和 | 通用进出场 |
'power2.out' |
干脆 | 卡片弹出(日常最常用) |
'power3.out' |
有力 | Hero 大标题出现 |
'power4.in' |
强冲击 | 弹出式通知 |
'back.out(1.7)' |
弹性过头 | 弹窗、Modal 进入 |
'elastic.out(1, 0.3)' |
橡皮筋 | 趣味交互、游戏 |
'bounce.out' |
弹跳 | 购物车图标、通知 |
'expo.in' |
加速 fade | 页面过渡 |
三个变体:ease.in(缓入)、ease.out(缓出)、ease.inOut(缓入缓出)。
3.2 实践建议
日常开发中,90% 的场景用 power2.out 就够了。它够干脆,不拖泥带水。花里胡哨的 ease 在真实产品中容易让用户觉得"卡"。
// 日常 90% 的场景
gsap.from('.thing', { y: 30, opacity: 0, duration: 0.5, ease: 'power2.out' })
// 弹窗
gsap.from('.modal', { scale: 0.8, opacity: 0, duration: 0.4, ease: 'back.out(1.4)' })
// 通知
gsap.from('.toast', { x: 50, opacity: 0, duration: 0.3, ease: 'power3.out' })
3.3 可视化挑选 Ease
GSAP 官网提供了 Ease Visualizer,可以直观对比所有 easing 曲线。你也可以用 GSAPify 的 Easing Guide 快速查阅。
4. Staggers——交错动画
这是一个让动画"活起来"的关键特性。
4.1 基础 Stagger
// 每个元素之间间隔 0.1s 出场
gsap.from('.card', {
y: 50,
opacity: 0,
duration: 0.5,
stagger: 0.1, // 间隔 0.1s
ease: 'power2.out'
})
4.2 Stagger 对象——精细控制
gsap.from('.card', {
y: 50,
opacity: 0,
duration: 0.5,
stagger: {
each: 0.1, // 每个之间 0.1s
from: 'center', // 从中间开始,向两边扩散(也可以是 'start' 'end' 'edges')
grid: [3, 4], // 按 3 行 4 列的网格排列
axis: 'x', // 先按 x 轴方向展开
}
})
4.3 Stagger 函数——完全自定义
gsap.from('.card', {
y: 50,
opacity: 0,
duration: 0.5,
stagger: (index, target, list) => {
// 奇数卡片从左边来,偶数从右边
return index % 2 === 0 ? 0 : 0.1
}
})
4.4 Stagger + Cycle 组合拳
gsap.from('.card', {
y: 60,
opacity: 0,
duration: 0.5,
stagger: 0.08,
rotation: gsap.utils.wrap([5, -3, 2]), // 轮换:第 1 个转 5 度,第 2 个转 -3 度...
})
这种"小而精"的细节,把普通动画提升到生产级水平。
5. Keyframes——另一种编排方式
如果你的动画只需要在一个元素上做多阶段变化,Keyframes 比 Timeline 更简洁:
gsap.to('.box', {
keyframes: [
{ x: 100, duration: 0.5 },
{ y: 100, duration: 0.5 },
{ x: 0, duration: 0.5 },
{ y: 0, duration: 0.5 },
],
ease: 'power2.inOut',
repeat: -1
})
这个例子让 .box 走一个矩形路径循环。
Keyframes vs Timeline 的选择:
- Keyframes:单一目标、多阶段变化
- Timeline:多个目标、需要交错编排
6. Callbacks & 事件
gsap.to('.box', {
x: 200,
duration: 1,
onStart() { console.log('开始') },
onUpdate() { console.log('更新中,progress:', this.progress()) },
onComplete() { console.log('完成') },
onRepeat() { console.log('重复次数:', this.repeat()) },
onReverseComplete() { console.log('反向播放完成') },
})
// 也可以传入参数
gsap.to('.box', {
x: 200,
duration: 1,
onComplete: myFunc,
onCompleteParams: ['box moved', 42],
})
7. 实用工具方法
// gsap.utils 工具箱
const random = gsap.utils.random(-100, 100) // 随机数
const pick = gsap.utils.random(['red', 'blue', 'green']) // 随机取
const distribute = gsap.utils.distribute({ base: 0, amount: 360 }) // 均匀分布
const snap = gsap.utils.snap(45) // 吸附到最近 45 度
const clamp = gsap.utils.clamp(0, 100) // 钳制范围
const mapRange = gsap.utils.mapRange(0, 1, 0, 360) // 范围映射
const interpolate = gsap.utils.interpolate('#fff', '#000') // 插值
// 实际应用:随机散射粒子
const particles = gsap.utils.toArray('.particle')
particles.forEach((p, i) => {
gsap.to(p, {
x: gsap.utils.random(-200, 200),
y: gsap.utils.random(-200, 200),
rotation: gsap.utils.random(-180, 180),
duration: gsap.utils.random(1, 3),
repeat: -1,
yoyo: true,
ease: 'sine.inOut',
})
})
8. 性能原则
- 用
x/y而不是left/top——避免 layout 重排 - 用
opacity和transform而不是width/height——走 GPU 合成 - 给动画元素加
will-change——提前提升到合成层(但要慎用,不要滥用) - Timeline 不等于并行——默认顺序执行,需要同时播放的设置
<位置参数 - 移动端降低复杂度——用
gsap.matchMedia()做响应式动画
实操清单
- 用
gsap.to()让页面上一个.box元素向右移动 200px,duration 设为 1s - 改用
gsap.from()实现同一个元素的"淡入滑入"效果(y: 30, opacity: 0) - 用
gsap.fromTo()完全指定起点和终点值,观察与from()的行为差异 - 保存 Tween 到变量,依次调用
.pause()/.resume()/.reverse()验证播放控制 - 创建一个
gsap.timeline({ defaults: { duration: 0.6, ease: 'power2.out' } }),用链式调用让三个元素依次出场 - 在 Timeline 中练习位置参数:分别用
'-=0.2'、'<'、'>'观察时序变化 - 用
tl.addLabel('section')打标签,再用tl.seek('section')跳转,验证标签导航 - 打开 GSAP Ease Visualizer,对比
power2.out、back.out(1.7)、elastic.out(1, 0.3)的曲线差异,并在代码中替换使用 - 给一组
.card元素添加stagger: 0.1,再改成stagger: { each: 0.1, from: 'center' }观察入场方向差异 - 用
keyframes数组让一个元素沿矩形路径循环运动(x→y→back x→back y),并与等效的 Timeline 写法做对比 - 给一个 Tween 添加
onComplete回调,在动画结束时打印日志或触发下一步操作 - 用
gsap.utils.random()和gsap.utils.toArray()实现多个粒子的随机散射动画