一次性弄懂回流和重绘

回流(Reflow)重绘(Repaint)

什么时候会触发回流或重绘呢?

当我们对dom 进行修改当时候会引发它外观(样式)上的改变时,就会触发回流或重绘。
这个过程本质上还是因为我们对 DOM 的修改触发了渲染树(Render Tree)的变化所导致的
一次性弄懂回流和重绘

我们回顾一下浏览器渲染页面的流程

1.根据 HTML 结构生成 DOM 树
2.根据 CSS 生成 CSSOM
3.将 DOM 和 CSSOM 整合形成 RenderTree
4.根据 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 队列的任务出队——这就是所谓的“不得已”时刻。

dom

相关推荐