你真的了解回流和重绘吗
回流和重绘是每个 Web 开发者都会听到的词,但具体两步各自做什么,很多人并不清楚。下面从浏览器渲染过程讲到如何减少它们,并配上可点的 demo。
渲染过程来自 MDN:解析 HTML/CSS → 生成渲染树 → Layout(回流,算几何)→ Painting(重绘,算像素)→ 交给 GPU 显示。回流一定会带着重绘,重绘不一定回流。
| 概念 | 算什么 | 典型触发 |
|---|---|---|
| 回流 Layout | 位置、大小等几何信息 | 增删可见 DOM、改尺寸/位置、窗口变化 |
| 重绘 Paint | 可见节点的绝对像素 | 改颜色、背景等不影响几何的样式 |
| 强制同步布局 | 读 offsetWidth 等立刻清空队列 | 循环里边读边写最伤 |
一、浏览器的渲染过程
若只想看优化手段,可跳到后面「减少回流和重绘」。完整链路如下:
-
解析 HTML 生成 DOM 树,解析 CSS 生成 CSSOM 树 -
两者结合生成渲染树(Render Tree) -
Layout(回流):根据渲染树得到节点几何信息(位置、大小) -
Painting(重绘):结合几何信息得到节点绝对像素 -
Display:像素发给 GPU。GPU 会把多个合成层合并;CSS3 硬件加速则是新建合成层,这里不展开
生成渲染树
浏览器大致会:从 DOM 根开始遍历每个可见节点;为每个可见节点匹配 CSSOM 规则;按节点和样式组成渲染树。不可见节点包括 script、meta、link,以及 display:none。visibility 和 opacity 隐藏的节点仍在渲染树上。渲染树只包含可见节点。
回流
还需要计算节点在视口内的确切位置和大小。浏览器从渲染树根开始遍历。例如:
第一个 div 是视口宽度的 50%,第二个是父节点的 50%。回流阶段会按视口实际宽度换成像素:
重绘
知道哪些节点可见、样式和几何信息之后,把每个节点变成屏幕像素,这一步叫重绘。
二、何时发生回流重绘
页面布局或几何信息变化时就要回流,例如:
-
添加或删除可见的 DOM 元素 -
元素位置变化 -
尺寸变化(margin、border、height、width 等) -
内容变化,比如文本变了,或图片换成不同尺寸 -
页面第一次渲染(不可避免) -
浏览器窗口尺寸变化
回流一定触发重绘,重绘不一定回流。改变范围大时可能整页重排,例如出现滚动条或改了根节点。
三、浏览器的优化机制
每次重排都有额外计算,所以多数浏览器会把修改放进队列,攒一段时间或达到阈值再批量执行。但读取布局信息会强制刷新队列,例如:
-
offsetTop / offsetLeft / offsetWidth / offsetHeight -
scrollTop / scrollLeft / scrollWidth / scrollHeight -
clientTop / clientLeft / clientWidth / clientHeight -
getComputedStyle()、getBoundingClientRect -
更完整的列表见 https://gist.github.com/pauli…
这些 API 必须返回最新布局,浏览器只能先清空队列。改样式时尽量避开;要用就先把值缓存起来。
四、减少回流和重绘
最小化重绘和重排
合并多次 DOM / 样式修改再一次处理。下面三次改几何,理论上三次回流;现代浏览器常优化成一次,但若中间读了布局信息,仍可能三次:
可以合并改变:
-
使用 cssText -
修改 CSS class
批量修改 DOM
步骤:让元素脱离文档流 → 多次修改 → 再放回。第一步和第三步可能回流,但中间修改因不在渲染树上而不会回流。三种办法:隐藏后再显示;用 document fragment 在文档外建子树再拷回去;拷到脱离文档的节点上改完再替换。
直接循环插入每次都会回流:
隐藏元素、应用修改、再显示(展示和隐藏各一次重绘):
使用文档片段:
拷贝到脱离文档的节点再替换:
针对上述情况有一个demo测性能,结果并不理想——浏览器本来就会排队合并多次修改,所以这套不必优先考虑。
避免触发同步布局
循环里每次读 box.offsetWidth 再写 p 的 width,会迫使浏览器每轮都先应用上一次样式:
先把宽度读出来再循环:
对比demo差距更明显。
复杂动画用绝对定位脱离文档流
复杂动画会频繁回流重绘,用绝对定位脱离文档流,避免带动父元素和后续兄弟。可看这个例子:控制台会输出当前帧数。
帧数一直到不了 60;点按钮设成绝对定位后可以稳定 60。
五、CSS3 硬件加速
更希望根本不要回流重绘。使用 CSS3 硬件加速,transform、opacity、filters 这些动画可以不引起回流重绘;background-color 等仍会,但性能往往也好一些。本文只谈用法。
常见触发属性:transform、opacity、filters、will-change。
用 Chrome Performance 抓一段回流重绘,见这个例子:
动画进行时没有发生回流重绘。注意:给太多元素开硬件加速会占内存;GPU 绘制字体可能抗锯齿失效,动画结束不关掉会产生字体模糊。
六、参考文献
-
渲染树构建、布局及绘制 -
高性能 Javascript
一句话总结:改几何就会回流,回流一定重绘;合并写样式、缓存布局读数,复杂动画再考虑脱离文档流或 GPU 合成层。
转载请注明来源:你真的了解回流和重绘吗












