算法与数据结构 / 动画引擎原理 · 从插值到播放控制 / 时序:重复、往返与停顿 待审核 5 / 47
repeat · loop / reverse / mirror · repeatDelay

时序:重复、往返与停顿

缓动回答的是「一次迭代之内,进度怎么走」。它上面还有一层:时序(timing)负责把物理流逝的毫秒翻译成「现在是第几次迭代、这次迭代内进度是多少」。一段动画可以重复若干次(repeat),两次重复之间可以停顿(repeatDelay),而重复的方向有三种互不相同的语义。

这两层的分工很干净:时序层只算出「迭代序号 + 迭代内线性进度」这一对数,方向语义与缓动曲线一概不管;拿到这对数之后再交给塑形函数,才产生真正驱动动画的值。

1 · 三种重复方向

loop 每次都从头顺放,进度走到 1 后瞬回 0;reverse 在奇数次迭代上把时间反演;mirror 在奇数次迭代上把值翻转。把整段重复沿总时间展开画成一条曲线,三者的形状差别一目了然。

图 1-1 · 一次迭代被重复与翻转后的完整进度曲线。可调重复次数、方向与迭代间停顿,下方代码面板随控件同步给出对应的 Animation 配置。

2 · 方向语义的分岔点

reversemirror 的差别只发生在塑形这一步。reverseease(1p)\text{ease}(1-p):先翻时间再缓动,缓动曲线本身随之时间反演。mirror1ease(p)1 - \text{ease}(p):先缓动再翻值,运动关于中点对称,去程与回程的节奏完全一致。

警示 · 缓动对称时两者形状相同。默认的 easeInOut 满足 ease(1p)=1ease(p)\text{ease}(1-p) = 1 - \text{ease}(p),切换 reversemirror 看不出任何区别——这一点上手时很容易误判为「配置没生效」。把缓动换成单向的 easeOut 才能看出分岔,图 1-1 的缓动选择器因此默认给的是 easeOut 而非引擎默认值。

3 · 停顿落在哪里

repeatDelay 插在两次迭代之间,而非迭代之内:进度走到 1 后并不立刻重置,而是停在末端等这段停顿走完,再开始下一次。曲线上表现为顶部(或往返的两端)多出一段水平平台。

整段动画的总时长因此是

delay+(repeat+1)duration+repeatrepeatDelay\text{delay} + (\text{repeat} + 1) \cdot \text{duration} + \text{repeat} \cdot \text{repeatDelay}

停顿只出现在迭代之间,所以乘的是 repeat 而不是 repeat + 1——最后一次迭代结束后不再等待。

单次迭代内的曲线本身见缓动;把多个互不相同的动画排到一条时间轴上(而非同一段反复重复)见关键帧编排;让这条时序被一个可暂停、可反向的游标驱动见播放控制