在了解重绘与重排之前,我们先来了解下浏览器的大致渲染过程,以便我们更好的理解重绘与重排。

浏览器的渲染过程

  1. 构建 DOM 树:渲染引擎解析 HTML 文档,首先将标签转换成 DOM 树中的 DOM node(包括JS生成的标签),构建 DOM 树;
  2. 构建 CSSOM 树:解析对应的 CSS 样式文件(包括 JS 生成的样式和外部 CSS 样式),构建 CSSOM 树;
  3. 构建渲染树(Render Tree):CSSOM 树构建结束后,和 DOM 树一起生成渲染树。渲染树中每个 node 都有自己的 style,而且渲染树不包含隐藏的节点(比如 display:none;),因为这些节点不会用于呈现;
  4. 布局渲染树(Layout/reflow):有了 Render Tree 后,从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标;
  5. 绘制渲染树(Painting/repaint):遍历渲染树,将各个节点绘制在屏幕上;

重绘和重排

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为重排。

在网页生成的时候,至少会渲染一次。在用户访问过程中,还会不断的进行渲染。

下面三个操作会导致网页进行重新渲染:

  • 修改 DOM;
  • 修改样式表;
  • 用户事件;

重新进行渲染就需要重新生成布局重新绘制,前者就叫做重排(回流),后者则叫做重绘

注意:重绘不一定会引发重排,但重排必定会引发重绘!


触发条件

触发重绘的条件:改变元素外观属性。例如:colorbackground-color 等等。

触发重排的条件:

  1. 页面渲染初始化(这是无法避免的);
  2. 浏览器窗口尺寸改变(resize 事件发生时);
  3. 元素位置的改变,使用动画;
  4. 元素尺寸的改变:大小,外边距,边框;
  5. 设置 style 属性;
  6. 激活 CSS 伪类(例如::hover);
  7. 添加或删除可见的 DOM 元素;
  8. 填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变;
  9. 查询或调用某些元素属性,比如:
    • offsetTop/offsetLeft/offsetWidth/offsetHeight
    • scrollTop/scrollLeft/scrollWidth/scrollHeight
    • clientTop/clientLeft/clientWidth/clientHeight
    • width/height
    • getComputedStyle()
    • currentStyle(IE)

注意:table 元素的重排和重绘成本要高于 div 元素,因为 table 元素及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等的其他元素要花很多时间,这就是尽量避免使用 table 布局页面的原因之一。


提高性能的技巧

  1. 由于浏览器会尽量把所有的变动都集中在一起,排成一个队列,然后一次性执行,尽量避免多次重新渲染。因此,在 DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作;
  2. 如果某个样式是通过重排得到的,那么最好缓存结果,避免下一次用到的时候,浏览器又要重排;
  3. 不要一条条地改变样式,而要通过改变 class,或者 csstext 属性,一次性地改变样式;
  4. 先将元素设为 display: none(需要1次重排和重绘),然后对这个节点进行 100 次操作,最后再恢复显示(需要 1 次重排和重绘)。这样就只用两次重新渲染;
  5. position 属性为 absolute 或 fixed 的元素,重排的开销会比较小,元素脱离了文档流,它的变化不会影响到其他元素;
  6. 尽量使用离线 DOM,而不是真实的网面 DOM,来改变元素样式。比如,操作 Document Fragment 对象,完成后再把这个对象加入 DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点;
  7. 只在必要的时候,才将元素的 display 属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility: hidden; 的元素只对重绘有影响,不影响重排;
  8. 使用 window.requestAnimationFrame()window.requestIdleCallback() 这两个方法调节重新渲染;

参考链接

网页性能管理详解 - 阮一峰