算法与数据结构 / 动画引擎原理 · 从插值到播放控制 / 经典动画原则:翻译成引擎参数 待审核 15 / 47
principles · 自然 · 引擎参数

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

其余各页讲引擎能算什么,本页反过来:那些让运动「像真的」的经验法则,具体落到哪个参数上。迪士尼总结的十二条原则里,有五条能干净地映射成引擎参数,且映射之后会发现——它们都不是引擎的新能力,而是关键帧、缓动、弹簧这几个既有原语的用法。

与 CSS 侧的好动画的七条准则互为表里:那边讲什么时候该动、动多久,本页讲同一段运动怎么算得更像真的。

1 · 缓入缓出

真实物体启动要加速、停下要减速,匀速反而最不自然。引擎里就是把 easelinear 换成 easeInOut——一个参数的事,却是五条里最基础也最常被略过的一条。

图 1-1 · 同一段位移在 linear 与 easeInOut 下的差别。可切换开关对照。

2 · 预备动作

大动作之前先反向蓄力,观众的眼睛被预告,动作因而更有力。引擎里是在关键帧序列前面插一个反向的途经点:x: [0, -16, 目标],再用 times 把蓄力压在前 22% 的时间里。

图 2-1 · 关键帧序列前插入反向途经点前后的对照。

注 · 蓄力量与时间占比要一起调。蓄力段若不给 times 而均分时间,反向那一小段会占掉三分之一的时长,看上去不是「蓄力」而是「先往回走了一趟」。图 2-1 里 16 px 的蓄力配 22% 的时间,量小而快,观感才对。

3 · 挤压与拉伸

有弹性的物体受力时形变而体积守恒:触地一刻横向铺开、纵向压扁。引擎里是给 scaleXscaleY 各排一条反相的关键帧,与位置同步,触地点最扁。transform-origin 要设在底部,否则压扁时物体会同时向上收,看着像悬空。

图 3-1 · 下落触地的形变。可切换开关对比无形变的僵硬版本。

警示 ·「体积守恒」在二维里的严格写法是 sxsy=1s_x \cdot s_y = 1,而图 3-1 用的是 0.6×1.4=0.840.6 \times 1.4 = 0.84,并不守恒。这是有意的:严格守恒对应的横向系数是 1/0.61.671/0.6 \approx 1.67,铺得太开,在这个尺寸的圆球上看起来像被踩扁的煎饼。手绘动画里这条原则本就是感觉优先,不是物理约束。

4 · 跟随与重叠

主体停下时,附属部分因惯性继续往前、再回弹归位。引擎里:主体用 easeOut 干脆停住,尾巴改用弹簧带过冲地追上来。两者起点相同、落定不同步,重叠感正来自这个时间差。

图 4-1 · 主体与附属部分的落定差。开关关闭时两者刚性同步平移。

5 · 弧线

生物的肢体绕关节转动,轨迹几乎都是弧线;直线位移看起来机械。引擎里让位置的两个分量各走各的:x 匀速推进,y 走一条 [0, -72, 0] 的抛物线,合成出弧。

图 5-1 · x 匀速配 y 抛物线合成的弧线,与直线位移的对照。
import { animate } from '@vega/anim';

// 缓入缓出: 整体 ease
animate(el, { x: [0, end] }, { ease: 'easeInOut' });

// 预备: 关键帧序列前插一个反向途经点 (蓄力)
animate(el, { x: [0, -16, end] }, { times: [0, 0.22, 1], ease: 'easeOut' });

// 挤压拉伸: scaleX / scaleY 反相, 与位置同步 (触地点最扁), origin 在底部
animate(el, {
  y:      [0, floorY, 0],
  scaleY: [1, 0.6, 1],   // 压扁
  scaleX: [1, 1.4, 1],   // 铺开
}, { ease: 'easeInOut' });

// 跟随 / 重叠: 主体干脆停, 附属用弹簧带过冲追上
animate(body, { x: [0, end] }, { ease: 'easeOut' });
animate(tail, { x: [0, end] }, { type: 'spring', spring: { stiffness: 180, damping: 12 } });

// 弧线: x / y 分量各走各的, 合成出弧
animate(el, { x: [0, end], y: [0, -72, 0] }, { ease: 'linear' });

沿任意曲线运动的通法见沿路径运动;关键帧与 times 的机制见关键帧与编排;弹簧的三种形态见弹簧