前端

reflow、repaint 与 transform 为何高效

回流与重绘分别对应布局树与绘制指令的重新计算,以及为何 transform 动画大多不拖累渲染主线程。

#browser #rendering #performance

reflow、repaint 与 transform 为何高效

上一篇 浏览器渲染流水线 讲的是「从零画一页」。页面跑起来之后,改 DOM 或样式会触发 局部重算。面试里常说的 回流(reflow)重绘(repaint),指的就是流水线里哪些阶段要重跑。

什么是 reflow(回流)?

Reflow 的本质是重新计算布局树(Layout)。

凡是会影响布局树的操作——改宽高、边距、显示方式、插入删除节点等——都需要重新做 Layout 及之后的阶段(分层、绘制、分块、光栅化、合成)。

浏览器会合并多次改动

连续多次改布局相关属性时,浏览器往往 先合并,等当前 JS 执行块结束后再统一算布局,避免同一帧里反复 reflow。

因此:改属性触发的 reflow 常常是异步批量的。

读布局属性会强制同步 reflow

如果 JS 在改完布局后又 读取 offsetWidthgetBoundingClientRect() 等依赖最新几何信息的属性,浏览器必须 立刻算出最新布局,否则会返回过期数据。

所以常见性能建议:先读后写,或批量改样式,避免读写交错。

读写交错可能触发强制同步布局

javascript
const el = document.querySelector('.box')
el.style.width = '200px'
// 读宽度会迫使浏览器立即 reflow
console.log(el.offsetWidth)
el.style.width = '300px'

什么是 repaint(重绘)?

Repaint 的本质是重新生成绘制指令(Paint)及之后步骤。

改的是 可见样式但不改布局 时——例如 colorbackgroundvisibility(在仍占位的情况下)——可能只需 Repaint,不必完整 reflow。

布局信息本身也是可见结果的一部分,所以:

Reflow 一定会引起 Repaint。

操作类型典型属性至少触发的阶段
ReflowwidthmargindisplayLayout → … → Draw
RepaintcolorbackgroundPaint → … → Draw

为什么 transform 效率高?

transform 不改变布局树,也 不改变绘制指令的生成方式(不触发典型意义上的 layout / paint 重算)。它主要影响流水线最后的 Draw(合成) 阶段:在合成线程里对已有位图做位移、旋转、缩放。

因此:

  • transform 变化时,渲染主线程 往往仍很空闲
  • 主线程再忙,也不应挡住 合成线程上的 transform 动画

对比:用 top / left 做位移动画会改布局,通常走 Reflow 路径,代价大得多。

注意: 对比的前提是 画面一样、实现不同。下面 demo 两个小球同步左右移动,左边用 transform,右边用 left;视觉上应一致,差别在每一帧走合成还是 Layout。

transform vs left:同动画、不同实现

Live Preview · 可编辑代码

正在加载代码预览…

小结

  • Reflow = 重算布局树,代价高;浏览器会批量,但 读布局属性会强制同步
  • Repaint = 重算绘制及之后;reflow 必然连带 repaint。
  • transform 主要在合成阶段完成,适合做位移、旋转、缩放动画;位移动画优先 transform,慎用 top / left