一次性弄懂回流和重绘
回流(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 队列的任务出队——这就是所谓的“不得已”时刻。
相关推荐
Vue和React是数据驱动视图,如何有效控制DOM操作?能不能把计算,更多的转移为js计算?因为js执行速度很快。patch函数-->patch,对比tag,对比tag与key,对比children