动画引擎原理 总览待审核
compositor · transform/opacity · reflow

该动什么:合成层 vs 布局/绘制

一帧画面要经过布局 (layout)绘制 (paint)合成 (composite) 三个阶段。 动画每帧改哪个 CSS 属性,决定了浏览器要从这条管线的哪一层重新算起:动 left / top / width / height / margin 会触发 layout,整条管线从头跑;动 color / box-shadow / background 跳过 layout 但仍要 paint;而动 transform / opacity 只需 composite,布局与绘制的结果可直接复用,且这一步交给 合成器线程 (compositor thread) 处理,不占主线程。下面用同一个左右往返动画做对照:左侧每帧改 left(并故意读 offsetWidth 制造 layout thrashing),右侧用 transform: translateX,实测每帧耗时与估算 fps。

1 · layout

算每个盒子的几何 (位置 / 尺寸)。left/width/margin 改它。

2 · paint

把盒子栅格化成像素图层。color/shadow/bg 改它。

3 · composite

把现成图层挪位 / 调透明度。transform/opacity 只到这。

动 left(触发 layout)

每帧改 left + 读 offsetWidth 强制同步布局

平均帧时 ms fps

动 transform(只触发 composite)

每帧改 translateX,布局 / 绘制结果复用

平均帧时 ms fps
本次对照 点「同时跑」开始测量
layout thrashing:在一个循环里先样式 (el.style.left = …) 再 几何 (el.offsetWidth),会逼浏览器立刻同步重算布局来回答这次读取,缓存里攒着的批量 更新被反复打断。这是上面 left 一侧故意制造的最坏情形——读写交错地放大 layout 的代价。

哪个属性落在管线的哪一层,决定了它能不能便宜地动。下表是常见动画属性的归属:

属性触发的最早阶段每帧代价
transform / opacitycomposite低(合成器线程,可不占主线程)
color / background / box-shadow / border-radiuspaint中(重新栅格化图层)
left / top / width / height / margin / paddinglayout高(几何重排,后续 paint + composite 跟着重跑)
为何 transform / opacity 便宜:盒子一旦被提升为独立的合成层 (compositing layer),它的 像素已栅格化缓存好;平移、缩放、改透明度只是给这张现成纹理一个新的变换矩阵 / alpha,由合成器 线程在 GPU 上完成,主线程哪怕忙于 JS 也不阻塞它。will-change: transform 提前告诉 浏览器「这个元素将要变换」,促其预先建层、避免动画开始那一刻的建层抖动——但它有显存成本,只标 真正要动的元素,动完可移除。
两种写法对照 — 同一段位移,代价天差地别

  

本页测的是「动什么属性」;当布局确实必须变 (如卡片换位、尺寸切换),用 高级交互 里的 FLIP 技术:先量布局前后的位置差,把变化倒推成一段 纯 transform 动画,从而避开逐帧重排。每帧到底由谁驱动、如何做到帧率无关,见 驱动器与帧