G1:Tween & Timeline 核心——GSAP 动画的基石

GSAP 动画实战系列 系列总览 | G1 Tween & Timeline 核心 ← 本文 | G2 ScrollTrigger | G3 文本动画 | G4 SVG 动画 | G5 GSAP + React | G6 AI 项目实战 | G7 社区案例


# Tween & Timeline 核心 ## Tween 三种创建方式 - gsap.to() 从当前到目标 - gsap.from() 从指定到当前 - gsap.fromTo() 完全控制 ## 核心属性 - duration delay repeat yoyo - ease stagger - onComplete onUpdate callbacks ## Timeline 编排 - tl.to / from / fromTo - 位置参数 "+=0.5" "-=0.2" ">" - add() addLabel() - play pause reverse ## Eases 缓动函数 - power1-4 back bounce - elastic circ expo - sine steps CustomEase ## Staggers 交错 - 数值 / 对象 / 函数 - 与 grid/cycle 的组合 ## Keyframes 关键帧 - 数组语法 - 与 Timeline 的选择

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 调自定义曲线

Easing 曲线示意图 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. 性能原则

  1. x / y 而不是 left / top——避免 layout 重排
  2. opacitytransform 而不是 width / height——走 GPU 合成
  3. 给动画元素加 will-change——提前提升到合成层(但要慎用,不要滥用)
  4. Timeline 不等于并行——默认顺序执行,需要同时播放的设置 < 位置参数
  5. 移动端降低复杂度——用 gsap.matchMedia() 做响应式动画

下一篇:G2 ScrollTrigger——滚动动画实战

实操清单

  • 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.outback.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() 实现多个粒子的随机散射动画