前端性能优化 - 浏览器重排和重绘

485 阅读5分钟

在了解浏览器的重排和重绘之前,我们需要知道的前置知识是:

在浏览器输入了一个 URL 到页面展示的过程是什么?

以简单的 GET 请求为🌰:

  1. 用户输入访问的 URL 地址。浏览器会先根据这个 URL 查看 浏览器缓存 - 系统缓存 - 路由器缓存,若缓存中有,直接跳到第 6 步操作,若没有,则按照下面的步骤进行操作。
  2. 浏览器根据输入的 URL 地址解析出主机名。
  3. 浏览器将主机名转换成服务器 IP 地址。浏览器先查找本地 DNS 缓存列表,看缓存里面是否存在这个 IP ,如果有则进入第 4 步,如果缓存中不存在这个 IP 地址,就再向浏览器默认的DNS服务器发送查询请求,同时缓存当前这个 IP 到 DNS 缓存列表中。
  4. 拿到 IP 地址后,浏览器再从 URL 中解析出端口号。
  5. 拿到 IP 和端口后,浏览器会建立一条与目标服务器的 TCP 连接,也就是三次握手
  6. 浏览器向服务器发送一条 HTTP 请求报文。
  7. 服务器向浏览器返回一条 HTTP 响应报文。
  8. 关闭连接,浏览器解析文档(渲染页面)。
  9. 如果文档中有资源则重复 6、7、8 动作,直至资源全部加载完毕。

接下来,就是浏览器对 HTML 的渲染,大致可以分为如下几步:

  1. HTML HTML 解析器解析成 DOM 树, css 则被 css 解析器解析成 CSSOM Tree 。
  2. DOM 树和 CSSOM Tree 解析完成后,被附加到一起,形成渲染树(Render Tree)。
  3. 节点信息计算(重排),根据渲染树计算每个节点的几何信息。
  4. 渲染绘制(重绘),根据计算好的信息绘制整个页面。

以上 4 步就是一次简单的浏览器渲染的过程。

好了,知道了过程就可以进入主题了~~

什么是重排和重绘

我们知道浏览器绘制一个页面需要下载以下几个部分的内容:

HTML 标记 / CSS / JS / 文件

下载完这些内容后会解析生成两个内部数据结构:

DOM 树渲染树

  • DOM 树 —— 表示页面结构,DOM树中的每一个需要显示的节点在渲染树种至少存在一个对应的节点(隐藏的DOM元素disply值为none 在渲染树中没有对应的节点)
  • 渲染树 —— 表示DOM节点如何显示。渲染树中的节点被 , 我们常说的 盒模型,也就是这个盒子。

一旦 DOM树渲染树 构建完成,浏览器就开始显示(绘制)页面元素。

那么问题来了,什么时候发生重排 和重绘呢?

  • 当 DOM 节点 的变化影响了元素的物理属性(宽高等),浏览器需要重新计算元素的物理属性值,同样其他元素的几何属性和位置也会因此受到影响。这时渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为重排

  • 完成重排后,浏览器会重新绘制受影响的部分到屏幕,该过程称为重绘

但是并不是所有的 DOM 变化都会影响几何属性,比如改变一个元素的背景色并不会影响元素的宽和高,这种情况下只会发生重绘。

重排发生的情况

从上面我们可以知道:重排必然会导致产生重绘,但是重绘不一定会重排。

所以我们可以来概括下重排发生的情况:

  1. 添加或删除可见的 DOM 元素
  2. 元素位置改变
  3. 元素尺寸改变
  4. 元素内容改变(例如:一个文本被另一个不同尺寸的图片替代)
  5. 页面渲染初始化(无法避免)
  6. 浏览器窗口尺寸改变

知道了重排何时发生,我们才能知道如何避免重排的发生。比如说,这些都是显而易见的,我们有时候在调试的时候,不间断地改变浏览器窗口大小,导致页面反应迟钝,这就是一次次的重排重绘导致的。

如何避免重排

一般来说,重排只影响渲染树中的一小部分,但也可能影响很大的部分,甚至整个渲染树。

浏览器所需要重排的次数越少,应用程序的响应速度就越快。

因此当页面顶部的一个动画推移页面整个余下的部分时,会导致一次代价昂贵的大规模重排,让用户感到页面一顿一顿的。渲染树中需要重新计算的节点越多,情况就会越糟。

所以我们可以从以下几个地方着手避免重排:

  1. 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)
  2. 同一个 DOM 的多个属性改变可以写在一起(减少 DOM 访问,降低强制渲染队列刷新的风险)
  3. 如果要批量添加 DOM ,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排(例如 fragment 元素的应用)
  4. 将需要多次重排的元素,设置 positionabsolutefixed ,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。