驱动器与帧:delta-time 与帧率无关
动画要动,必须有一处「时间在流动」。这就是驱动器 (driver):浏览器里它是
requestAnimationFrame,每次屏幕刷新回调一次,并送来一个单调递增的时间戳。引擎其余部分
都是纯函数,真正让时间前进的只有这一层。问题在于:刷新间隔不是常数——144Hz 屏约 7ms 一帧,
60Hz 约 16.7ms,掉帧时更可能跳到 30ms 以上。若每帧都按「固定一步」推进,动画速度就会随帧率漂移。
正确做法是只信两帧之间的真实间隔 delta-time,按 pos += v * dt 累加——这就是
帧率无关 (frame-rate independent)。下面两颗球同跑:
蓝球每帧固定 +x(帧率依赖),
橙球按 delta-time 累加(帧率无关),
黑色参考线标出「按真实经过时间应到达的位置」。
固定步进 (蓝):每帧固定 +2px · delta 累加 (橙):+ v·dt · 黑线:应到达处
应到
固
end
应到
Δt
end
经过时间 0ms
本帧 dt —ms
已渲染帧数 0
固定步进偏差 0px
delta 偏差 0px
15 fps:蓝球(固定步进)立刻明显落后于黑色参考线,而橙球
(delta-time)始终贴线——掉帧只是让它每帧走一大步,总距离仍然正确。这正是「每帧 +1」式
代码在低端设备 / 后台标签页里跑得更慢、不同设备表现不一致的根因:把帧数当成了时间。
Animation.tick(timestamp) 做的
正是此事:第一帧只记录 lastTimestamp 不推进,之后每帧
delta = timestamp − lastTimestamp,再 currentTime += delta × speed。
时间来源被收敛到 driver 这一层后,把 rafDriver 换成 manualDriver、由测试逐帧
投喂确定的时间戳,动画就完全可复现、可断言——这就是「手动 driver = 可确定性测试」。
每帧固定步进 (帧率依赖) vs delta-time 累加 (帧率无关)
@vega/anim — Animation.tick 用 delta × speed 累加 currentTime
同一套「唯一时间来源 + 纯函数求值」也支撑起播放控制:暂停只是停止累加、
seek 只是改游标、reverse 只是 speed 取负。当运动没有固定时长、由初速度与物理参数决定时,
时间同样按 dt 推进给求解器,见弹簧。