动画引擎原理 总览待审核
rAF · delta-time · 帧率无关

驱动器与帧: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」式 代码在低端设备 / 后台标签页里跑得更慢、不同设备表现不一致的根因:把帧数当成了时间。 delta-time 修正的前提是「同一个 dt 喂给所有运动」。引擎的 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 推进给求解器,见弹簧