算法与数据结构 / 动画引擎原理 · 从插值到播放控制 待审核 47 页

动画引擎原理 · 从插值到播放控制

动画看上去是「元素动起来」,本质是一连串纯粹的数值问题:给定起点、终点与一个 0→1 的进度,求中间值 (插值);把均匀流逝的时间重新塑形成有快慢的节奏 (缓动);或干脆交给一个物理系统自己演化到静止 (弹簧)。再往上是把多个值串成途经点 (关键帧)、把多段动画排在一条时间轴上 (时间线 / 交错),最后用一个游标和播放速率把这一切组织成可暂停、可 seek、可反向的播放器。

本系列刻意不依赖 CSS transition / animation——那会把这些要素藏进浏览器内部。这里每一页都由本仓库手写的 @vega/anim 引擎逐帧计算并驱动:左边动真实元素,右边画出「值随时间」的曲线,两相对照,看清每个要素的数学本质。引擎本体在 packages/anim,带完整单元测试。

地基:值怎么算、时间怎么塑形

interpolate · mix · 线性光

插值:中间值怎么求

动画最底层的要素是插值:给定起点、终点与进度 p 求中间值。数字按比例取,带单位的字符串拆出数字再回贴单位,颜色则须先解码到线性光再混合,否则中点发灰。

color space · OKLCH · HSL hue

色彩空间:沿什么路径混合颜色

两色之间的插值不是一个点而是一条路径。sRGB 通道平均中点发灰,线性光先解码再混合,HSL 沿色相环且方向可选,OKLCH 在感知均匀的坐标里走。

easing · cubic-bezier · steps

缓动:把时间重新塑形

缓动不改变起点与终点,只把均匀流逝的时间轴重新映射成有快慢的值进度。引擎只定义 in 曲线,out 与 in-out 由翻转与镜像变换得到;cubic-bezier 需 Newton 迭代反解。

cubic-bezier · 控制点 · Newton 反解

缓动曲线编辑器:拖控制点

cubic-bezier 的形状全由两个控制点决定。手柄线的斜率即起止瞬时速度,控制点的 y 越界即产生过冲;引擎驱动时走的是「给定时间反解参数」的路径,而画曲线不必反解。

repeat · loop / reverse / mirror · repeatDelay

时序:重复、往返与停顿

时序层把物理毫秒翻译成「第几次迭代、迭代内进度多少」。重复方向的三种语义 loop / reverse / mirror 只在塑形一步上分岔,repeatDelay 则落在两次迭代之间。

number tween · odometer · 格式化

滚动数字:计数器与翻牌

数字补间的难点不在动画而在呈现。纯计数器只需取整加千分位;翻牌 odometer 若按齿轮式取每位的商,静止时会停在两数之间,须改用进位传递。

物理:不给时长的弹簧

编排:关键帧 · 时间线 · 交错

keyframes · timeline · stagger

关键帧与编排:途经点、时间轴、交错

把一对 from→to 推广出三件要素:关键帧用 times 把「经过哪些值」与「在哪个进度经过」解耦;时间线把多段动画排在一条轴上;交错为一组元素批量生成递增延迟。

sequence · timeline · 2D stagger

编排进阶:序列、依赖、二维交错

序列在排布时一次算定布局,依赖式串接把下一段交给 onComplete 因而容得下运行时才确定的时长,二维交错则把网格坐标折成一条空间轴再交给同一个 stagger。

split-text · stagger · typewriter

文本动画:逐字、逐词、打字机

split-text 是「N 个动画各配一个 delay」,typewriter 是「1 个动画驱动一个整数游标」。前者拆 DOM 靠 stagger 编排,后者把离散的敲字事件挂到连续进度上。

text morph · 槽位对齐 · 一个时钟

文字变形:一个词逐字长成另一个词

两串字先对齐成一组槽位,再由唯一一个 Animation 驱动全部字。render 是时间到画面的纯函数,seek 与反向因而免费;代价是打断时的「接住」要自己实现。

principles · 自然 · 引擎参数

经典动画原则:翻译成引擎参数

迪士尼那批经验法则对应到引擎里都不是新能力,而是关键帧、缓动与弹簧几个既有原语的用法。逐条给出朴素与应用原则的对照。

galton · 预定关键帧路径 · 交错投放

高尔顿钉板:随机过程与确定性播放器

随机只发生在开局抽路径那一刻,之后的下落是对一条确定折线的采样。因而这台物理玩具不需要碰撞检测,且天然可 seek、可反向。

程序化:周期与噪声的无尽动画

不演化到目标、没有起止的另一类运动——三角函数驱动的规则周期与噪声驱动的有机微动。

控制与交互:可回退的播放器

play · pause · reverse · seek · speed

播放控制:为什么动画可以回退

播放器只持有游标与播放速率两样状态。因为「时间到值」是纯函数管线,暂停是停止累加、seek 是直接改游标、反向只是给 speed 取负。

indeterminate loop · deferred target · 减速落定

转盘落点:目标未知时先空转再落定

点击时落点还在接口路上,先匀速空转占位,返回后按当前角速度新建一段减速。默认用 decay 让时长由物理反推,再用 modifyTarget 吸附到中奖扇区。

easeReverse · 换向缓动 · anchor

换向缓动:reverse 时别把 ease 倒过来

speed 取负会把缓动曲线一起时间反演,慢收尾倒放成慢启动。easeReverse 在换向点记锚,对剩余距离重施正向缓动。

drag · scroll-linked · FLIP

高级交互:拖拽惯性、滚动驱动、FLIP

三类交互动画都不引入新机制,只是把输入接到动画值上,或把播放头接到别的来源上。FLIP 的 Invert 必须在首帧之前同步写上。

interrupt · velocity handoff · redirect

中断与重定向:带速度接管

打断时位置总是连续的,差别只在速度。读出当前速度交给新弹簧当初速度,运动才首尾如一;清零则是一次生硬的顿挫。

rAF · delta-time · 帧率无关

驱动器与帧:delta-time 与帧率无关

引擎其余部分都是纯函数,真正让时间前进的只有驱动器这一层。按帧数累加会随刷新率漂移,按两帧之间的真实间隔累加才帧率无关。

animateTo · spring 值源 · velocity 连续

飞行中重定向:animateTo 不丢速度地改目标

原生弹簧值源让值由物理实时产出,animateTo 以当前显示值与当前速度为新初始条件重建弹簧;固定曲线值源只能 cancel 再起一条,衔接处丢速度。

time remap · slow-mo · speed ramp

时间重映射:局部慢放与速度曲线

在均匀播放头之上再套一层「时间到时间」的映射。慢放区省下的时间必须在区间外补回,因此两侧局部速度必然大于 1。

determinate · indeterminate · trickle · segmented · circular

进度条:把跳变的真实进度译成可信的运动

进度条要解决的不是画一个矩形,而是把离散跳变、甚至不可知的外部信号,在时间上重新塑形成连续可信的运动。五种形态各对应一种信号。

variants · state-driven · 声明式

声明式状态与 variants

预先声明一组命名状态,每个状态只是一张目标属性表;运行时只切换当前状态,每个属性各自用一根弹簧从此刻的值过渡到新目标。

一条主线

插值 (值) → 缓动 / 弹簧 (时间塑形) → 关键帧 / 时间线 (组合) → 游标 + speed (播放控制)。每一层都是纯函数,只有最外层的驱动器 (requestAnimationFrame) 让时间真正流动——这就是整个引擎可被确定性测试、动画可被 seek 与反向的根本原因。

性能与调度:该动什么、怎么驱动

渲染管线代价、合成层、单 rAF 与读写分离——让动画不掉帧的工程地基。

SVG:几何与笔触

把前面的插值与缓动接到 SVG 的可动画属性上——同一套「时间→值」,落到笔触长度、路径弧长、取景框与形状上。

与平台对照

手写引擎之外,浏览器原生提供了哪些动画能力,各自的边界在哪。

空间:3D 变换与透视

延伸阅读