一、什么是回流和重绘
回流:对DOM元素的修改涉及到了DOM几何尺寸的改变,浏览器需要重新计算元素的几何属性
重绘:对DOM元素的修改仅仅改变了DOM的颜色等属性,没有涉及几何尺寸的改变,直接重新计算元素的样式
回流一定会引起重绘,但是重绘不一定引起回流。不可否认的是回流和重绘都非常消耗性能,所以我们要尽可能避免。但是在页面初次加载的时候一定会引起回流和重绘,这也是开销最大的一次回流与重绘。
二、如何避免
2.1 哪些会产生
避免回流与重绘,我们得先明白有哪些操作能引起它们,才能更好的避免
1、直接获取元素的width、length、border等几何属性值肯定会引起回流
2、在JS中对于一些特定属性值的获取是一定会引起回流的,如:
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
3、调用getComputedStyle()方法
2.2 如何去避免
1、针对一些特定属性值的多次获取,先利用JS缓存起来,避免多次获取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#el {
width: 100px;
height: 100px;
background-color: yellow;
position: absolute;
}
</style>
</head>
<body>
<div id="el"></div>
<script>
// 获取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";
}
</script>
</body>
</html>
在循环中,每次循环都会获取offsetTop和offsetLeft属性值,会产生大量的回流,考虑先把属性值获取,保存为js变量,再进行循环
// 缓存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"
2、对于多次设置几何属性值,将他们放到一个类中,合并样式
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
上述代码逐次设置集合属性,每次设置都会引起回流,将他们合并到一个类中,只会产生一次回流
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.basic_style {
width: 100px;
height: 200px;
border: 10px solid red;
color: red;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const container = document.getElementById('container')
container.classList.add('basic_style')
</script>
</body>
</html>
3、将DOM离线化
只有元素在页面上才会引起回流与重绘,我们可以把需要修改样式的元素,先display:none,不让他显示在页面上,等所有属性都设置完成,在将它显示出来。
const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
...(省略了许多类似的后续操作)
离线化之后:
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'
这种方法只适合DOM操作很多的情况
4、将回流的元素脱离标准流,这样不会影响其他元素
5、避免设置多层内联样式,每一个都会引起回流,样式合并到外部类
三、浏览器中真实的回流与重绘(flush队列)
let container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
分析上面的代码,可能会得到结果进行了3次回流,4次重绘,但是结果却是1次回流,1次重绘。这是为什么呢?浏览器会把要回流和重绘的任务都放到一个队列中,等到一定时间或者一定数量的时候才一起执行。
但是不是所有引起回流的任务都会放到队列里,延迟执行,对于那些特定属性值(offsetTop等)的获取,浏览器是要马上执行的,因为这讲究及时性。