anime.js Timeline 详解:控制多动画顺序与并行执行

作者:小圆点

时间:2026-11-03

浏览:589

深入详解 anime.js Timeline(时间轴)核心机制,系统剖析通过 offset 参数精准控制多动画串行等待、重叠交错与完全并行的实现方法,提供完整代码示例与生产避坑指南。

文章目录
在前端界面开发中,复杂动效往往由多个微小属性变化组合而成。如果单纯通过 setTimeout 或深层嵌套的 complete 回调函数去协调各元素的先后时序,代码很容易陷入时序漂移与难以维护的混乱状态。anime.js 提供的 Timeline(时间轴)机制通过单一虚拟时钟将分散的子动画纳管至统一轨道,利用简洁的 offset 参数即可实现精准的串行等待、重叠交错与同步并行。
anime.js Timeline 详解:控制多动画顺序与并行执行
图 0:anime.js Timeline 多轨道调度与时序控制概览

一、单时钟编排:为何需要 Timeline

当我们要为一个卡片设计进场动效时,通常伴随着遮罩层淡入、卡片主体位移放大、内容文字逐行滑入这三个动作。直接使用多个独立的 anime() 实例会遇到一个明显问题:如果遮罩耗时 400ms,卡片就必须显式设置 delay: 400;若随后视觉要求将遮罩时长调整为 500ms,后续所有动画的 delay 都要人工重新计算累加。

此外,当用户在动画未播完时突然关闭弹窗,需要立即倒放(reverse)或直接暂停(pause)所有动作时,分散的动画实例无法实现协同同步,容易产生部分元素已停而另一部分仍在播放的画面撕裂。

anime.timeline() 从根本上解决了这一痛点。它构建了一个父级时间容器,内部子动画共享同一个全局时间进度游标。子动画的执行不再依赖系统时钟或硬编码的绝对延迟,而是挂载在时间轴的时间刻度上,支持链式组合与整体控制。

anime.js Timeline 详解:控制多动画顺序与并行执行
图 1:anime.js Timeline 构建与链式编排四步流程

二、时间轴基础构建与链式调用

构建一个时间轴首先需要调用 anime.timeline() 创建实例,然后使用 .add() 方法按逻辑顺序挂载具体的动画描述对象。每一个 .add() 都会返回时间轴实例自身,因此可以采用链式调用。

// 引入并初始化时间轴容器
const tl = anime.timeline({
  autoplay: true,           // 创建后是否立即启动播放,默认为 true
  loop: false,               // 是否循环整个时间轴
  direction: 'normal',       // 播放方向:normal | reverse | alternate
  defaults: {               // 注入全局默认属性,后续所有子动画默认继承
    duration: 600,
    easing: 'easeOutQuad'
  }
});

// 链式追加动画子项
tl.add({
  targets: '.modal-backdrop',
  opacity: [0, 1]
})
.add({
  targets: '.modal-window',
  translateY: [40, 0],
  opacity: [0, 1]
})
.add({
  targets: '.modal-content',
  scale: [0.96, 1],
  opacity: [0, 1]
});

在默认情况下,只要不提供第二个参数,.add() 就会采取严格的串行模式:后一个动画会精准等待前一个动画运行结束的那一毫秒才开始执行。这保证了基本逻辑的直观与可预测。

三、核心机制:Time Offsets 参数深度解析

实现动画顺序还是并行的关键,就在于 .add(parameters, [offset]) 的第二个可选参数 offset。通过改变 offset 的传入方式,我们可以自由操纵子动画在时间轨道上的起点位置。

anime.js Timeline 详解:控制多动画顺序与并行执行
图 2:Time Offsets 原理对照:串行等待、留白延后、交错重叠与绝对同拍

1. 默认追加(省略 offset 或 '+=0')

若省略 offset,其语义等同于相对上一个动画结束时刻增加 0 毫秒。前序动画耗时 600ms,当前动画就在第 600ms 起跑;若前序动画时长被修改为 1000ms,当前动画自动后移至第 1000ms 触发,无需人工介入。

2. 相对正偏移('+=300')

表示在前一个动画完全结束之后,额外留出 300 毫秒的视觉空白或停顿期,再启动当前动画。这常用于节奏呼吸感的营造,避免元素紧接着闪现给用户带来仓促感。

3. 相对负偏移('-=400')—— 实现交错并行的核心

这是动效设计中最常用的手法。传入字符串 '-=400',意味着当前动画在前一个动画尚未播放完毕、还剩 400 毫秒时提前触发。此时,前一个动画的收尾帧与当前动画的起始帧在时间上形成重叠窗口,产生流畅自然的并行衔接效果。

4. 绝对时间戳数值(如 0 或 800)

若 offset 传入一个无符号的纯数字(如 0 或 1200),时间轴将其视为以整个时间轴起始时刻为基准的绝对毫秒刻度。

  • 传入 0:即使它写在第三个 .add() 中,也会立刻与时间轴起点对齐,与第一个动画同时并行起跑。
  • 传入 800:不论前面的动画执行了多长时间、添加了多少步,该动画都必定在时间轴行进到第 800ms 时精准启动。

5. 对齐符号('<' 与 '<+=100')

在 anime.js v3 中,还支持使用 '<' 符号将当前动画与上一个动画的开始时刻直接对齐。如果要让当前动画在上一个动画开始后的 100ms 启动,可以书写为 '<+=100',这为群组协同动效提供了极大便利。

语法注意:相对偏移量('-=200'、'+=150')必须包裹在字符串引号中。如果误写成数字 -200,时间轴会因无法识别相对前缀而无法得到预期效果,甚至导致时序计算异常。

四、并行执行的两种实战形态与代码实现

在 UI 交互中,“并行执行”通常细分为两类业务形态:一种是多属性/多目标完全同拍起跑;另一种是层级交错的瀑布式(Cascade)微并行。

形态一:完全同拍并行(多元素独立缓动)

有时我们需要卡片在从下往上飞入的同时,背景色进行渐变,且文字逐渐放大。因为这三个动作适合的缓动曲线不同(位移适合 easeOutCubic,而透明度适合 linear),我们不能将其合并为一个动画,但又必须让它们同时起跑。

const heroTimeline = anime.timeline();

heroTimeline
  // 动画 1:容器位移,耗时 800ms
  .add({
    targets: '.hero-card',
    translateY: [60, 0],
    duration: 800,
    easing: 'easeOutBack'
  })
  // 动画 2:完全同拍并行(offset: 0),耗时 400ms,独立采用 linear 渐变
  .add({
    targets: '.hero-card',
    opacity: [0, 1],
    duration: 400,
    easing: 'linear'
  }, 0)
  // 动画 3:背景光晕同拍并行(offset: 0),耗时 1000ms
  .add({
    targets: '.hero-glow',
    scale: [0.8, 1.2],
    opacity: [0, 0.6],
    duration: 1000,
    easing: 'easeOutQuad'
  }, 0);

形态二:级联交错并行(Cascade Overlap)

在列表展开或菜单抽屉滑出时,如果等到菜单面板完全滑出之后文字才一条一条出现,会显得界面反应迟钝;但如果完全同时出现,又缺乏层次感。最佳体验是菜单滑到约 60% 时,第一批列表项就交错出现。

const menuTl = anime.timeline();

menuTl
  // 菜单侧边栏滑出,持续 500ms
  .add({
    targets: '.drawer',
    translateX: ['-100%', '0%'],
    duration: 500,
    easing: 'easeOutCubic'
  })
  // 提前 250ms(即侧边栏行进到一半时)列表项交错登场
  .add({
    targets: '.drawer-nav-item',
    translateX: [-20, 0],
    opacity: [0, 1],
    delay: anime.stagger(60),  // 列表子元素逐个微延迟
    duration: 350,
    easing: 'easeOutQuad'
  }, '-=250');

五、属性继承与局部覆写规则

anime.timeline({ defaults }) 的继承机制是保持大型动效风格统一的重要保障。然而理解其作用边界十分关键:

  • 可继承属性:duration、delay、easing、round 等补间计算参数。当子动画没有单独指定时,自动回退到 defaults 的值。
  • 局部覆写:子动画对象中声明的同名属性具有最高优先级。例如默认 duration: 600,但某个装饰线动画显式传入 duration: 200,则该子动画以 200ms 运行,不影响后续其他子动画。
  • 时间轴专属属性(不被子项继承):autoplay、loop、direction 属于时间轴全局控制参数,子动画无法也不应单独声明这些字段。

六、全局播放控制与时间游标联动

时间轴的一个显著优势,在于把全部子动画收归为一个拥有统一播放控制 API 的对象。无论是正向播放、暂停还是倒放,均作用于整个时间轴全局。

anime.js Timeline 详解:控制多动画顺序与并行执行
图 3:Timeline 内部游标与播放状态控制模型
控制方法 内部时钟行为 典型应用场景
tl.play() 从当前游标位置沿既定方向持续推进时间 点击触发、显式播放
tl.pause() 冻结当前游标位置,停止 requestAnimationFrame 调度 鼠标悬停、页面失焦、弹窗中断
tl.restart() 将时间游标强制重置到 0ms 并重新开始推进 重置按钮、重新演示动效
tl.reverse() 反转时间推进方向,从当前刻度向反方向递减 折叠面板收起、弹窗关闭过渡
tl.seek(targetMs) 解除时间流逝,将游标瞬间跳转至指定毫秒数并重绘画面 滚动条驱动(Scroll-tied)、滑块拖拽预览
// 结合 HTML Range 滑块实时擦碰(Scrubbing)动画
const slider = document.querySelector('#progress-slider');

slider.addEventListener('input', () => {
  // 将 0-100 的滑块百分比转换为时间轴对应的毫秒刻度
  const targetTime = (slider.value / 100) * tl.duration;
  tl.seek(targetTime);
});

七、实践中的常见陷阱与注意事项

在实际项目落地时,以下几点是开发者使用时间轴时最容易踩坑的细节:

1. 异步动态追加 .add() 导致总时长计算失真

时间轴在初始化执行链同步执行完后,就会立刻预计算出整个时间轴的 duration。如果在 fetch 或 setTimeout 等异步回调中动态调用 tl.add(),会破坏原有的时钟计算,导致 loop、reverse 或 complete 回调无法按预期触发。正确做法是将全部序列在同步阶段一次性声明完整。

2. CSS Transform 复合属性的相互冲撞

当多个子动画在重叠并行的时间段内同时作用于同一个 DOM 节点的 transform(例如动画 A 在改 translateX,并行动画 B 在改 scale),如果未合理隔离目标层级,后一个补间可能会覆盖前一个属性写入。对于复杂的同级并行,推荐拆分父子容器(外层做位移,内层做缩放),或者在同一个 .add() 中同时声明多个属性。

3. 回调函数的层级归属

anime.timeline({ complete: fn }) 里的 complete 是在整条时间轴的所有动画全部走完时才触发一次;而写在某个具体 .add({ complete: fn }) 里的回调,只代表该子阶段结束。务必根据业务需要将回调绑定在对应的层级上。

anime.js Timeline 详解:控制多动画顺序与并行执行
图 4:anime.js Timeline 顺序与并行控制决策全景速查图

八、总结与时序控制速查

掌握 anime.js Timeline 的核心在于理解“单一虚拟时钟”与“相对时间偏移”两个概念:

  • 需要严谨的分步交代时,省略 offset 或使用 '+=0',依托天然的串行队列;
  • 需要有机衔接、消除等待感时,利用 '-=xxx' 提前交错重叠,让上一动效尾部与下一动效头部并行;
  • 需要多通道同拍爆发或绝对时间锚定时,使用绝对数值(如 0)或 '<' 符号实现多轨精确同步。

通过合理运用这些模式,即可摆脱混乱的定时器嵌套,以工程化、可维护的方式驾驭复杂生动的前端动画编排。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章