目录
  1. 1. 回流
  2. 2. 重绘
  3. 3. 导致回流或重绘的操作
  4. 4. 如何避免
    1. 4.1. 用变量临时保存值
    2. 4.2. 避免逐条改变样式,使用类名去合并样式
    3. 4.3. 将 DOM “离线”
  5. 5. Flush 队列:浏览器并没有那么简单
reflow(重排) && repaint(重绘)

回流

当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。

重绘

当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

导致回流或重绘的操作

最“贵”的操作:改变 DOM 元素的几何属性

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

“价格适中”的操作:改变 DOM 树的结构

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

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

涉及到即时计算的:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight。同时getComputedStyle 和 currentStyle也会。

如何避免

用变量临时保存值

1
2
3
4
5
6
7
// 获取el元素
const el = document.getElementById('el')
// 这里循环判定比较简单,实际中或许会拓展出比较复杂的判定需求
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
12
13

// 缓存offsetLeft与offsetTop的值
const el = document.getElementById('el')
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"

避免逐条改变样式,使用类名去合并样式

1
2
3
4
5
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
const container = document.getElementById('container')
container.classList.add('basic_style')

将 DOM “离线”

我们上文所说的回流和重绘,都是在“该元素位于页面上”的前提下会发生的。一旦我们给元素设置 display: none,将其从页面上“拿掉”,那么我们的后续操作,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线化。

1
2
3
4
5
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
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 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。因此我们看到,上面就算我们进行了 4 次 DOM 更改,也只触发了一次 Layout 和一次 Paint。

虽然现代浏览器进行了优化处理,但为了那些不聪明的浏览器(说的就是你了IE),手动优化还是 很有必要的。

文章作者: Joe
文章链接: http://zhuowenzhou.com/2019/11/09/reflow-repaint/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 前端彼岸

评论