Fork me on GitHub

前端性能优化与原理实践之【回流和重绘】

前端性能优化与原理实践之回流和重绘!

一、什么是回流和重绘?

1、回流与重绘发生的原理

我们通过 JS 来改变 DOM 的结构和样式,就会触发 CSSOM 渲染树的重新构建,此时就引起来回流和重绘。

  • 回流: 如果对 DOM 的几何属性(高、宽)进行改变时,浏览器就会对元素的几何属性进行重新绘制,这个过程叫做回流
  • 重绘:修改元素的样式属性进行改变,却没对几何属性进行改变,浏览器不需要重新计算几何属性,只为元素绘制新的样式就可以了,这个过程叫做重绘

PS:重绘不一定导致回流,回流一定导致重绘。

二、造成回流和重绘的操作

1、改变 DOM 的集合属性(最高)

当一个DOM元素的几何属性发生变化时,所有和它相关的节点(比如父子节点、兄弟节点等)的几何属性都需要进行重新计算,它会带来巨大的计算量。

常见的几何属性有 width、height、padding、margin、left、top、border 等等

2、改变 DOM 的结构(适中)

主要指的是节点的增减、移动等操作。

3、获取特定的值(被忽略)

这些属性值都是需要实时计算才能得到的,因此浏览器得到这些值需要进行回流。

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

三、避免回流与重绘

1、频繁获取某些属性值

避免频繁的获取到某些引起回流的属性,我们通过给 JS 分压,将数据缓存起来,最后一次性进行操作 DOM。

1
2
3
4
for(let i=0;i<10;i++) {
el.style.top = el.offsetTop + 10 + "px";
el.style.left = el.offsetLeft + 10 + "px";
}
1
2
3
4
5
6
7
8
9
10
11
let offLeft = el.offsetLeft, offTop = el.offsetTop

// 在JS层面进行计算
for(let i=0;i<10;i++) {
offLeft += 10
offTop += 10
}

// 一次性将计算结果应用到DOM上
el.style.left = offLeft + "px"
el.style.top = offTop + "px"

2、合并改变样式的操作

我们改变样式避免逐条的改变,这样会频繁的引起重绘,我们通过一次性设置样式,从而只进行一次重绘。

1
2
3
4
5
6
7
8
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'

const container = document.getElementById('container')
container.classList.add('basic_style')

3、将 DOM 离线化

我们通常所说的回流和重绘都是在线上引起的,如果我们操作不在线的 DOM 就不会引起回流和重绘,我们将这种情况叫做离线化。

1
2
3
4
5
6
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
1
2
3
4
5
6
7
8
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 队列

现在的浏览器非常的聪明,会知道频繁的改动 DOM 会引起重绘和回流,那么于是它自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。

不得已的情况就是之前获取一些属性会引起回流和重绘的,因为需要实时的获取属性,所以提前在 Flush 中出队。