回流(Reflow)和重绘(Repaint)

目录

  1. 1. HTML 渲染过程
  2. 2. 回流(Reflow)和重绘(Repaint)
  3. 3. 引起回流和重绘的操作
  4. 4. 减少回流和重绘
    1. 4.1. 避免多次更改样式属性,合并为一个
    2. 4.2. 将要操作的元素“离线处理”
    3. 4.3. 减少操作影响的元素
    4. 4.4. 读取引起回流的属性,尽量缓存
  5. 5. 参考
  6. 6. 扩展阅读

最近在重看一些基础的理论知识,看到了回流(Reflow)和重绘(Repaint)的概念,惭愧的发现,对此竟然说不出太详细的内容,在此参阅一些文章进行一个总结。

在了解这两个概念之前先了解一下 HTML 的渲染过程。

HTML 渲染过程

浏览的引擎处理 HTML 的基本流程分为如下四个步骤:

1.解析 HTML 并构建 DOM 树和 CSSOM 树。浏览器对 HTML 进行解析,将 HTML 标记转换成文档对象模型 (DOM),CSS 标记则被转换成 CSS 对象模型 (CSSOM),而 DOM 和 CSSOM 是独立的数据结构。在解析过程中,DOM 树包含了所有的 html 标签,包括不展示的 head 节点和 display:none 的节点,而 CSSOM 树则会去掉浏览器不能识别的样式,比如不支持的浏览器前缀(chrome不支持的-moz-前缀)和 hack(如firefox不支持_开头的样式)。

2.构建 render 树。将 DOM 树和 CSSOM 树合并为 render 树,在这个过程中,需要计算每一个呈现对象的可视化属性,会去掉不展示在页面上的节点(如 display:none 和 head 节点等)。

3.布局 render 树。render 树在创建完成时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。从 render 树的根节点((对应于 HTML 文档的 元素))递归调用,计算每一个元素的位置和大小信息。

4.使用 render 树绘制页面。在绘制阶段,系统会遍历 render 树,将其内容显示在屏幕上。绘制的顺序其实就是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。
这就是浏览器引擎处理页面的基本流程了。接下来我们看一下回流和重绘的概念

回流(Reflow)和重绘(Repaint)

回流和重绘分别出现在上面的第三和第四步。

  • 回流(Reflow):当 render 树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。
  • 重绘(Repaint):当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响 render 树重新布局的,比如修改了元素的 background-color。

由上定义可以看出,回流必将引起重绘,而重绘不一定会引起回流。明显回流的成本比重绘的成本高得多。

引起回流和重绘的操作

任何对 render 树中元素的操作都会引起回流或者重绘:

  • 改变 DOM 树结构,比如添加或者删除可见的元素、改变文本内容、改变位置;
  • 改变元素几何尺寸:边距、填充、边框、宽度和高度;
  • 用户改变浏览器窗口尺寸;
  • CSS伪类激活,在用户交互过程中发生;
  • 获取某些属性,浏览器一般对引起回流、重绘的操作进行优化,将多个操作合并在固定时候执行,如果此时获取一些 style 信息,则会提前执行操作,以提供准确数值。常见的这类信息如:offsetTop/offsetLeft/offsetWidth/offsetHeightscrollTop/Left/Width/Height,具体可以查看 CSS Triggers

减少回流和重绘

减少回流、重绘其实就是需要减少对render 树的操作,合并多次 DOM 和样式的修改,并减少对一些style信息的请求。具体有:

避免多次更改样式属性,合并为一个

当更该样式的时候,可以更改class 或者将多个更改操作合并为一个:

// 不好的写法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";

// 比较好的写法
el.className += " className1";

// 比较好的写法
el.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";

将要操作的元素“离线处理”

主要注意三点:

  • 使用 DocumentFragment 进行缓存操作,引发一次回流和重绘;
  • 使用 display:none 技术,只引发两次回流和重绘;
  • 使用 cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;

减少操作影响的元素

将导致多次回流的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。

读取引起回流的属性,尽量缓存

// 不好的写法
for(;;) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}

// 这样写好点
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for(;;) {
left += 10;
top += 10;
s.left = left + "px";
s.top = top + "px";
}

参考

扩展阅读

知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。