乐趣区

一次性弄懂回流和重绘

回流(Reflow)重绘(Repaint)
什么时候会触发回流或重绘呢?
当我们对 dom 进行修改当时候会引发它外观(样式)上的改变时,就会触发回流或重绘。这个过程本质上还是因为我们对 DOM 的修改触发了渲染树(Render Tree)的变化所导致的
我们回顾一下浏览器渲染页面的流程
1. 根据 HTML 结构生成 DOM 树 2. 根据 CSS 生成 CSSOM3. 将 DOM 和 CSSOM 整合形成 RenderTree4. 根据 RenderTree 开始渲染和展示 5. 遇到 <script> 时,会执行并阻塞渲染

回流: 当我们对 dom 进行修改当时候,并且导致页面几何尺寸发生变化当时候(比如修改元素的宽、高或隐藏元素等),浏览器需要重新去计算元素当几何属性,并且同时会影响到其他元素当几何属性,然后将重新计算出来的结果绘制出来,这个过程就叫回流也叫重排。
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

所以由此来看 重绘不一定导致回流,回流一定会导致重绘 前面我们说回流和重绘是会对 dom 进行修改,会消耗性能,所以我们要尽可能减少回流和重绘的次数。
怎么减少回流和重绘呢?
要减少回流和重绘的发生,我们得知道那些操作会导致,
回流
1. 改变 dom 元素的几何属性常见的几何属性有 width、height、padding、margin、left、top、border 等等。此处不再给大家一一列举。有的文章喜欢罗列属性表格,但我相信我今天列出来大家也不会看、看了也记不住(因为太多了)。我自己也不会去记这些——其实确实没必要记,️一个属性是不是几何属性、会不会导致空间布局发生变化,大家写样式的时候完全可以通过代码效果看出来。2. 改变 dom 树的结构主要指的是增加或减少 dom 节点,移动等操作。3. 获取一些特定属性的值当你要用到像这样的属性:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 时,你就要注意了!
“像这样”的属性,到底是像什么样?——这些值有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。
除此之外,当我们调用了 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发回流。原理是一样的,都为求一个“即时性”和“准确性”。
重绘
回流比重绘更加消耗性能,付出的代价更高
那如何避免回流和重绘
1. 避免逐条改变样式,使用类名去合并样式
const container = document.getElementById(‘container’)
container.style.width = ‘100px’
container.style.height = ‘200px’
container.style.border = ’10px solid red’
container.style.color = ‘red’
将这段代码用 类名去合并
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
const container = document.getElementById(‘container’)
container.classList.add(‘basic_style’)
2. 将 DOM“离线”
let container = document.getElementById(‘container’)
container.style.display = ‘none’
container.style.width = ‘100px’
container.style.height = ‘200px’
container.style.border = ’10px solid red’
container.style.color = ‘red’
…(省略了许多类似的后续操作)
container.style.display = ‘block’
Flush 队列
继续用上面的代码来说明这个问题
const container = document.getElementById(‘container’)
container.style.width = ‘100px’
container.style.height = ‘200px’
container.style.border = ’10px solid red’
container.style.color = ‘red’
从这段代码中发生了几次回流与重绘呢?在这里改变了几何属性的有 width, height, border。改变颜色的 red, 所以导致了三次回流和一次重绘,一眼看过去是很正确的,其实是忽略了浏览器自身:并没有那么简单!这段代码实际上只进行了一次回流和一次重绘,为什么和我们开始想的不一样呢?因为现代浏览器是很聪明的。浏览器自己也清楚,如果每次 DOM 操作都即时地反馈一次回流或重绘,那么性能上来说是扛不住的。于是它自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。因此我们看到,上面就算我们进行了 4 次 DOM 更改,也只触发了一次 Layout 和一次 Paint。但是也有例外,因为有的时候我们需要精确获取某些样式信息,下面这些:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 这些属性有很强的“即时性”。当我们访问这些属性时,浏览器会为了获得此时此刻的、最准确的属性值,而提前将 flush 队列的任务出队——这就是所谓的“不得已”时刻。

退出移动版