动画即数据:把 timeline 序列化成 descriptor
前面各页都是「写代码 animate(el, …)」的命令式动画。换一种范式:把动画写成一份纯数据
(descriptor),再写一个解释器读它、用引擎播出来。这正是 Lottie 的本质——
它不是「更复杂的 SVG path 动画」,而是把 关键帧这套「时间→值」的 IR 序列化成 JSON,
再交给运行时渲染器逐帧插值。本页用 @vega/anim 手写一个最小版,并演示「动画是数据」带来的红利。
descriptor:把动画写成数据
一份 descriptor 描述的不是「画一条什么 path」,而是有哪些图层、每个图层的哪些属性、在哪些帧取什么值:
layers[] 每层一个图形 + 若干属性轨 (tracks);每条轨是一串关键帧
{ f, v, ease } (帧号、值、入此段的缓动)。单帧轨 = 静止值。下方的图形完全由右侧这份数据驱动——
改任意一个数字再「重新载入」,动画随之改变。
descriptor (可编辑 —— 改完点「重新载入」)
解释器:把 descriptor 编译成一个 Animation
解释器只做两件事:其一,把每条属性轨喂给 createKeyframes(values, { times, ease }) 得到一个采样器——
帧号按总长归一化成 times (落在 0→1),逐段缓动给到 ease;其二,建一个主
Animation 当播放头,它的 onUpdate(p) 把进度 p 喂给每条轨的采样器,
合成 transform / opacity 写到对应图层。整页就这点代码:
字段与引擎原语逐项对得上——descriptor 不过是这套引擎模型的序列化形式:
| descriptor 字段 | 含义 | @vega/anim 原语 |
|---|---|---|
tracks.x = [{f,v,ease}, …] | 一条属性的关键帧轨 | createKeyframes(values, { times, ease }) |
关键帧的 ease | 入此段的缓动曲线 | 逐段 ease 数组 (长度 = 段数) |
fps + duration (帧) | 总时长 | Animation.duration = 帧 / fps × 1000 |
多个 layers[] | 独立图层各跑各的轨 | 主 Animation 的 onUpdate 内分发给每层 |
| 整体播放 / 变速 / 跳转 | 播放器能力 | play / speed / seek / reverse 现成 |
shape.fill 一个值再重编译,编辑器里改任意数字亦然;其二,可 seek / 反向 / 变速——
因为整份数据编译成一个主 Animation,而「时间→值」是纯函数,
拖 seek 条、点反向、调 speed 全部白送 (Lottie 的 setSpeed / playSegments 同理);
其三,矢量且轻——数据里没有像素,任意分辨率不糊。与浏览器原生及 Lottie / WAAPI 的能力边界对照见
与原生对照。
每条轨的值如何插出来见插值,逐段快慢由缓动塑形, 多轨多层如何排在一条轴上见关键帧与编排;真实工业格式的细节 (After Effects → Bodymovin 导出、dotLottie 打包) 见CSS 之外的动画做法。