浏览器知识点整理(九)重排、重绘、合成是怎么回事?

2,267 阅读7分钟

前言

先来回顾一下浏览器 渲染流程 的大概过程:构建 DOM 树(DOM)、样式计算(Style)、获取布局树(Layout)、生成图层树(Layer)、图层绘制(Paint)、栅格化处理(Raster)、合成显示(DrawQuad)。

理解了浏览器的渲染流程之后,再去理解重排、重绘和合成的概念就很容易了。

重排

重排,也叫回流,是更新了元素的几何属性之后,浏览重新触发布局(Layout),更新完整的渲染流程的过程

它的关键 在于修改了元素的几何属性,例如改变元素的宽度、高度等,这个修改会让浏览器从布局开始更新整个渲染流水线,这个开销是最大的。如下图所示:

image.png

一些会引起重排操作的情况:

  • 添加或者删除可见的 DOM 元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

根据改变的范围和程度,渲染树中或大或小的部分需要重新计算,有些改变会触发整个页面的重排,比如,滚动条出现的时候或者修改了根节点。

重绘

重绘,因为没有引起几何位置的变化,所以浏览器会直接进入图层绘制阶段(Paint),然后执行之后的渲染流程

它的关键 在于更新的是绘制属性,不会引起几何位置的变化,例如修改了元素的背景颜色或者文字颜色,它会从图层绘制阶段开始更新渲染流水线,相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

所以说,重排必定会发生重绘,重绘不一定会引发重排

image.png

合成

从渲染流水线的角度看,修改布局属性会引起重排,修改绘制属性会引起重绘,那如果修改一个既不要布局也不要绘制的属性,渲染流水线又会怎么处理呢?

如果修改一个既不要布局也不要绘制的属性,渲染引擎将直接跳过布局和图层绘制的阶段,只执行后续的合成操作,这个过程叫做 合成

比如使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是很高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以 相对于重绘和重排,合成能大大提升绘制效率

image.png

浏览器的优化

现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会 通过队列机制来批量更新布局。浏览器会将修改操作放入到队列里,至少一个浏览器刷新(即16.6ms)才会清空队列。这样可以让多次的重排、重绘变成一次回流重绘。

但是,当在 获取布局信息 的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发重排与重绘来确保返回正确的值

主要包括以下属性或方法:

  • offsetTopoffsetLeftoffsetWidthoffsetHeight
  • scrollTopscrollLeftscrollWidthscrollHeight
  • clientTopclientLeftclientWidthclientHeight
  • widthheight
  • getComputedStyle()
  • getBoundingClientRect()

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会强制刷新渲染队列。如果要使用它们,最好将值缓存起来。

如何减少重排和重绘?

首先要知道为什么减少重排重绘能优化 Web 性能?

在渲染流水线的视角下,减少重排重绘,省去了布局和绘制阶段,这样少了渲染进程的主线程和非主线程的很多计算和操作,是能够加快页面的渲染展示的,而我们的目的正在于此。

如何减少重排和重绘?

浏览器本身会做一些优化,但是我们平时写代码也要注意避免重排和重绘:

在写 JS 代码时:

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
  • 避免频繁操作DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中;或者在修改时隐藏元素,修改完成之后再显示。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用 绝对定位,使它 脱离文档流,否则会引起父元素及后续元素频繁回流。

在写 CSS 代码时可以:

  • 使用 tranform 替代 toptranform 是合成属性,不会引起重排和重绘
  • 使用 visibility 替换 display:none,前者只会重绘,后者改变布局会引发重排
  • 避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局,即重排
  • 尽可能在 DOM 树的最末端改变 class ,重排是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变 class,可以限制了重排的范围,使其影响尽可能少的节点
  • 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多
  • 将动画效果应用到 position 属性为 absolutefixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是重排,同时,控制动画速度可以选择 requestAnimationFrame
  • 避免使用 CSS 表达式,可能会引发重排
  • 将频繁重绘或者重排的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。如 will-changevideoiframe 等标签,浏览器会自动将该节点变为图层。
  • CSS3 硬件加速(GPU加速),使用 CSS3 硬件加速,可以让 transformopacityfilters 这些动画不会引起重排重绘。但是对于动画的其它属性,比如 background-color 这些,还是会引起重排重绘的,不过它还是可以提升这些动画的性能。

总结

  • 重排 是更新了 元素的几何属性 之后,浏览重新触发 布局(Layout),更新完整的渲染流程的过程
  • 重绘 是修改了 元素的绘制属性 之后,浏览器会直接进入 图层绘制阶段(Paint),然后执行之后的渲染流程的过程
  • 重排必定会发生重绘,重绘不一定会引发重排
  • 合成 是修改一个既不要布局也不要绘制的属性之后,渲染引擎直接跳过布局和图层绘制的阶段,只执行后续的 合成操作 的过程。
  • 减少重排和重绘可以优化 Web 性能,浏览器通过队列机制来优化,平时写代码也要注意避免重排和重绘。