回流 (Reflow) 是指网页渲染引擎根据元素的尺寸、位置和显示属性来重新计算页面的排版和布局,是网页渲染过程中的一个重要步骤。重绘 (Repaint) 是指网页渲染引擎根据显示属性 (如颜色、文字大小等) 重新绘制页面元素,不影响元素的位置和尺寸。
通过有效控制回流和重绘,可以提高网页的渲染性能。本篇文章会从浏览器渲染过程,讨论到回流 (Reflow) 和重绘 (Repaint) 的概念,以及如何优化。
浏览器渲染过程
浏览器在渲染画面时,会经过几个过程:
-
解析 HTML 和样式计算 (parsing and style calculation) 把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并成渲染树 (render tree)。可参考下图所示:
-
布局 (Layout)
渲染树 (render Tree) 有 DOM 的结构和每个节点的样式,但这还不足以呈现页面,还需要计算个节点在画面上的大小和位置,这个过程称之为布局 (layout),并且这个过程会产生一个布局树 (layout Tree)。
-
绘制 (paint) 拥有 DOM、样式和布局仍然不足以呈现页面,浏览器仍然必须判断元素的绘制顺序。可以把这个过程想像成为绘画过程的注释 (paint record),例如:
- 首先是要画个背景
- 然后在 (x,y,w,h) 位置上是文字
- 然后再画个矩形
如下图所示
-
合成 (compositing)
前三个步骤中,浏览器已经获得了渲染页面所需的资讯,但为了提高整体渲染效率,浏览器会再透过合成 (compositing),将资讯渲染到画面上。合成 (compositing) 是一种将页面的各个部分分成图层 (layers) 的技术,而这个技术会在合成线程 (compositor thread) 这个单独的线程执行。在这个过程完成之后,还会再产生一个图层树 (layer tree),最终才会渲染到画面上。
回流 (Reflow) 和重绘 (Repaint)
了解完浏览器的渲染过程后,我们回到问题:浏览器中的回流 (Reflow) 和重绘 (Repaint) 是什么 ?以及如何优化?
回流 (Reflow) 和重绘 (Repaint) 指的就是渲染的布局 (layout) 和绘制 (paint) 的步骤。当我们做了某些事情改变布局或样式,就会触发回流 (Reflow) 或重绘 (Repaint)。
要注意的是,浏览器的渲染过程其实是有代价的,因为在渲染过程中,每个步骤都会使用上一个操作的结果来创建新数据。例如:如果布局树 (layout Tree) 改变,那就会需要重新绘制。所以如果能够尽量避免回流 (Reflow) 或重绘 (Repaint),就能够大大提升效能。
何时发生回流 (Reflow) 和重绘 (Repaint)
何时发生回流 (Reflow)?
影响浏览器效能很重要的关键因素,因为可能导致整个或部分页面的布局更新,可能因为一个节点大小的改变,就会触发整的页面的回流。例如:改变 width
、height
、font-size
等。
当一个元素的长与宽改变,可能会影响到画面中其他元素的编排,所以每当有一个元素的布局改变,浏览器的 CPU 需要重新计算整个页面中不同元素的长宽、间距等。在计算的期间,会没办法处理起他任务 (例如使用者在点击按钮,可能会要等一阵子才有回应,因为浏览器无暇处理)。
这也是为什么,回流对效能的影响,往往比重绘来得大。
何时发生重绘 (Repaint)?
当页面上的某个元素需要改变颜色或其他不影响布局的属性时,浏览器会对其进行重绘 (repaint)。与回流不同,重绘不会影响页面布局,但是也会影响页面的性能。例如:改变 outline
、visibility
、color
、background-color
等。
减少回流 (Reflow) 和重绘 (Repaint)
在浏览器渲染过程中最后一步骤是合成 (compositing),在某些情况,我们可以透过一些技巧只需要让浏览器合成 (compositing),而避免回流 (Reflow) 和重绘 (Repaint)。
以下提供几个方法:
技术手段 | 原因说明 |
---|---|
批量操作 DOM | 避免多次触发 reflow,使用 DocumentFragment 或 display: none 批量修改 |
使用 class 替代 style 多次修改 | 改 class 一次触发一次重排,style 连续改多次可能连续触发重排 |
避免频繁读写 layout 属性混用 | 如:连续读取 offsetTop 然后设置 style.top ,中间会插入 layout 计算流程 |
使用 transform / opacity 替代 top / left | transform 不会触发重排,只会触发合成层变化(GPU 加速) |
使用虚拟列表 | 减少页面中渲染节点数量 |
使用 will-change 属性 | 告诉浏览器元素将来的变化 |
body >.sidebar {
will-change: transform;
}