回流和重绘

一、是什么

HTML中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:

  • 回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置 - with/height/top/left

  • 重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性(效果)进行绘制 - color/background-color

在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变

当我们对 DOM 的修改引发了 DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来

当我们对 DOM的修改导致了样式的变化(colorbackground-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了回流

回流必将引起重绘,重绘不一定会引起回流。 性能影响:回流比重绘的代价要更高。

二、如何触发

要想减少回流和重绘的次数,首先要了解回流和重绘是如何触发的

回流触发时机

回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:

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

还有一些容易被忽略的操作:获取一些特定属性的值

offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight

这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流

除此还包括getComputedStyle方法,原理是一样的

重绘触发时机

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

例如:

  • 颜色的修改
  • 文本方向的修改
  • 阴影的修改

浏览器优化机制

由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列

当你获取布局信息的操作的时候,会强制队列刷新,例如元素的 offsetTop 等方法都会返回最新的数据

因此浏览器不得不清空队列,触发回流重绘来返回正确的值

三、如何减少

CSS:

  • 避免使用 table 布局。table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
  • 如果想设定元素的样式,通过改变元素的 class 类名,且尽可能在 DOM 树的最末端改变 class
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或fixed的元素上。使它脱离文档流,否则会引起父元素及后续元素频繁回流。
  • 避免使用 CSSJavaScript 表达式(例如:calc())。
  • 使用css3硬件加速,可以让transformopacityfilters这些动画不会引起回流重绘
  • 对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响

JavaScript:

  • 避免频繁操作样式:最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 避免频繁操作DOM:在使用 JavaScript 动态插入多个节点时, 可以使用DocumentFragment,创建后一次插入,就能避免多次的渲染性能
    • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性:如果确实需要多次使用,就用一个变量缓存起来。

参考文献

上次更新:
Contributors: jiangjingmin