know

面试经常提到的重排和重绘,你真的了解吗?

# 1. 重排(reflow)

# 1.1. 概念

当更新了元素的几何属性,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排,也称为“回流”。

例如通过 JS 或 CSS 修改了元素的宽度和高度,浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。

渲染树的节点发生改变,影响了该节点的几何属性,导致该节点位置发生变化,此时就会触发浏览器重排并重新生成渲染树。重排意味着节点的几何属性改变,需重新计算并生成渲染树,导致渲染树的全部或部分发生变化。

重排需要更新完整的渲染流水线,所以开销也是最大的。

# 1.2. 常见的引起重排属性和方法

任何会改变元素的位置和尺寸大小的操作,都会触发重排。常见的例子如下:

  • 添加或删除可见的 DOM 元素
  • 元素尺寸改变
  • 内容变化,比如在 input 框中输入文字
  • 浏览器窗口尺寸改变
  • 计算 offsetTop、offsetLeft 等布局信息
  • 设置 style 属性的值
  • 激活 CSS 伪类,例如 :hover
  • 查询某些属性或调用某些方法

# 1.2.1. 几何属性

几何属性:包括布局、尺寸等可用数学几何衡量的属性。

  • 布局:display、float、position、list、table、flex、columns、grid
  • 尺寸:margin、padding、border、width、height

# 1.2.2. 获取布局信息的属性或方法

获取布局信息的属性如下:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect()

频繁获取这些属性会强制清空浏览器的重排队列,建议避免频繁使用。

# 1.3. 重排的影响范围

浏览器渲染界面是基于流式布局模型的,所以触发重排时会对周围的 DOM 重新排列,影响的范围分两种:

# 1.3.1. 全局范围

从根节点 html 开始对整个渲染树重新布局。

<body>
  <div class="hello">
    <p><strong>Name:</strong>BDing</p>
    <h5>male</h5>
    <ol>
      <li>loving</li>
    </ol>
  </div>
</body>

上面代码中的 p 节点发生重排时,它的父节点 div 和 body 也会发生重排,甚至 h5 和 ol 节点也会受到影响。

# 1.3.2. 局部范围

对渲染树的某部分或某一渲染对象进行重新布局。

例如:将一个 DOM 元素的宽高等几何信息写死,然后在 DOM 元素内部触发重排,就只会重新渲染该 DOM 元素内部的元素,而不会影响到外界。

# 2. 重绘(repaint)

# 2.1. 概念

更新了元素的绘制属性,但没有改变布局,重新把元素外观绘制出来的过程叫做重绘。例如更改某些元素的背景颜色。

重绘并没有引起元素几何属性的改变,所以就直接进入绘制阶段,然后执行之后的一系列子阶段。

和重排相比,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

重排一定会伴随重绘,重绘却不一定伴随重排。

# 2.2. 外观属性

包括界面、文字等可用状态向量描述的属性

  • 界面:appearance、outline、background、mask、box-shadow、box-reflect、filter、opacity、clip、border-radius、background-size、visibility
  • 文字:text、font、word

# 3. 性能优化

重排和重绘在操作节点样式时频繁出现,同时也存在很大程度上的性能问题。重排成本比重绘成本高得多,因为一个节点的重排可能导致子节点、兄弟节点或祖先节点的重排,所以我们要尽可能减少重排次数、重排范围。

# 3.1. 使用 visibility:hidden 替换 display:none

  • display:none:不占据空间,触发重排重绘
  • visibility:hidden:占据空间,只触发重绘

# 3.2. 避免使用 Table 布局

Table 布局可能很小的一个改动就会造成整个 table 重排。通常可用 ul、li、span 等标签取代 table 系列标签生成表格。

# 3.3. 避免设置多层内联样式

浏览器的 CSS 解析器解析 css 文件时,对 CSS 规则是从右到左匹配查找,样式层级过多会影响重排重绘效率。应尽量避免写过于具体的 CSS 选择器,保证层级扁平。

# 3.4. 将频繁重绘或重排的节点设置为图层

将节点设置为 video 或 iframe,或为节点添加 will-change 属性,可以阻止节点的渲染行为影响别的节点。

# 3.5. 使用 requestAnimationFrame 作为动画帧

动画速度越快,重排次数越多。浏览器刷新频率为 60Hz,即每 16.6ms 更新一次,而 requestAnimationFrame()正是以 16.6ms 的速度更新一次。

# 3.6. 对于复杂动画效果,使用绝对定位让其脱离文档流

可以使用绝对定位,让其脱离文档流,避免父元素及后续元素频繁重排。

# 3.7. 动态改变类而不改变样式

使用新的类名预定义节点样式,在适合时机一次性动态替换原来的类名集合,避免频繁触发重排。

# 3.8. 避免触发同步布局事件

频繁访问布局属性会导致浏览器强制清空队列,进行强制同步布局。应在循环外使用变量保存不会变化的 DOM 映射值。

# 3.9. 批量修改 DOM

使元素脱离文档流,对其进行多次修改,最后再将元素带回到文档中。可以通过 display 隐藏、DocumentFragment 或复制节点等方式实现。

# 3.10. CSS3 硬件加速(GPU 加速)

使用 CSS3 硬件加速,可以让 transform、opacity、filters、will-change 这些动画不会引起重排重绘,但其它属性如 background-color 还是会引起重排重绘。

注意:为太多元素使用 CSS3 硬件加速会导致内存占用较大,GPU 渲染字体会导致抗锯齿无效。

# 4. 总结

  • 重排是因为元素的几何属性更改触发的
  • 重绘是由于元素的绘制属性更改触发的
  • 触发重排也一定会触发重绘,触发重绘不一定会触发重排
  • 重排的成本高于重绘
  • 减少重排次数、重排范围是 Web 性能优化的基本思路
上次更新: