调度:单 rAF 与读写分离
页面上同时跑着多个动画时,真正决定每帧成本的不是动画数量,而是调度方式。两个工程要点决定了
一帧能否在预算 (60fps 下约 16.7ms) 内完成:其一,所有动画应共用一个 requestAnimationFrame
循环,而非各自 requestAnimationFrame——后者带来重复的回调调度开销,且各动画拿到的
时间戳互不一致;其二,一帧之内把所有 DOM 读 (measure,如 getBoundingClientRect /
offsetTop) 批量做完,再批量 写 (mutate),避免「读-写-读-写」交错触发强制同步布局
(layout thrashing)。下面的对照可实测两种开关对帧时间的影响。
每个方块每帧读取自身布局位置,再据此写回 transform (制造真实的读写依赖)
模式 分离 · 单 rAF
采样帧数 0
rAF 回调数/帧 1
本次平均帧时间
— ms/帧
最优记录 (分离 · 单 rAF)
— ms/帧
先跑一次最优组合作为基线,再切换开关对比劣化幅度。
requestAnimationFrame,回调调度次数随元素数线性增长,且不同回调看到的时间戳出现微小漂移,
动画之间不再严格对齐。
交错 (读-写交替, 触发 thrashing) vs 分离 (先读后写, 单次 reflow)
把「唯一的时间来源」收敛成一个循环,正是引擎驱动器 (driver)的设计: 所有动画挂在同一个 rAF 驱动上,单次开销、时间戳一致、天然对齐;读写分离这一帧内的次序, 则与浏览器把样式与布局变更合批后交给合成器 (compositor)的流水线一脉相承。