动画引擎原理 总览待审核
single rAF · read/write batching · layout thrashing

调度:单 rAF 与读写分离

页面上同时跑着多个动画时,真正决定每帧成本的不是动画数量,而是调度方式。两个工程要点决定了 一帧能否在预算 (60fps 下约 16.7ms) 内完成:其一,所有动画应共用一个 requestAnimationFrame 循环,而非各自 requestAnimationFrame——后者带来重复的回调调度开销,且各动画拿到的 时间戳互不一致;其二,一帧之内把所有 DOM 读 (measure,如 getBoundingClientRect / offsetTop) 批量做完,再批量 (mutate),避免「读-写-读-写」交错触发强制同步布局 (layout thrashing)。下面的对照可实测两种开关对帧时间的影响。

每个方块每帧读取自身布局位置,再据此写回 transform (制造真实的读写依赖)

模式 分离 · 单 rAF 采样帧数 0 rAF 回调数/帧 1

本次平均帧时间

ms/帧

最优记录 (分离 · 单 rAF)

ms/帧

先跑一次最优组合作为基线,再切换开关对比劣化幅度。

关掉读写分离:每个方块变成「读一次布局 → 立刻写 transform → 下一个方块再读」的交错序列。 每次读都强制浏览器把上一次写产生的样式变更同步结算成新的布局 (forced synchronous layout), 元素越多,反复 reflow 的代价越呈倍数放大,帧时间显著上升。关掉单 rAF:每个方块各自注册一次 requestAnimationFrame,回调调度次数随元素数线性增长,且不同回调看到的时间戳出现微小漂移, 动画之间不再严格对齐。
交错 (读-写交替, 触发 thrashing) vs 分离 (先读后写, 单次 reflow)

  

把「唯一的时间来源」收敛成一个循环,正是引擎驱动器 (driver)的设计: 所有动画挂在同一个 rAF 驱动上,单次开销、时间戳一致、天然对齐;读写分离这一帧内的次序, 则与浏览器把样式与布局变更合批后交给合成器 (compositor)的流水线一脉相承。