# 概念
回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。
TIP
重绘不一定导致回流,回流一定会导致重绘。
# 回流的触发条件
- 改变 DOM 的几何属性,如 width、height、padding、margin、left、top、border 等等。
- 改变 DOM 树的结构,如 DOM 节点的增、删、移动等。
- 获取一些特定的属性值,如 offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight。因为这些属性的值都是通过即时计算才能得到,所以获取这些值也会触发回流。
# 重绘的触发条件
以上触发回流的操作都会触发重绘制,除此之外修改背景、颜色、通过 visibility: hidden 隐藏一个 DOM 元素等也会触发重绘。
# 优化办法
# 避免频繁改动 DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
const box = document.getElementById('box');
for (let i = 0; i < 10; i++) {
box.style.left = box.offsetLeft + 10 + 'px';
}
</script>
</body>
</html>
下面这样可以减少不必要的 DOM 操作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
div {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
const box = document.getElementById('box');
let offsetLeft = box.offsetLeft;
for (let i = 0; i < 10; i++) {
offsetLeft += 10;
}
box.style.left = offsetLeft + 'px';
</script>
</body>
</html>
# DocumentFragment
DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const fragment = document.createDocumentFragment()
for (let i = 0; i < 5; i++) {
const span = document.createElement('span');
span.innerHTML = i;
fragment.appendChild(span)
// document.body.appendChild(span)
}
document.body.appendChild(fragment);
</script>
</body>
</html>
# 使用类合并修改样式
const box = document.getElementById('box');
box.style.width = '200px';
box.style.height = '200px';
box.style.border = '1px solid red';
box.style.padding = '10px';
上面这样对 box 进行了多次操作,会频繁触发回流,我们可以用一个类来合并这些修改,降低 DOM 操作。
.box {
width: 200px;
height: 200px;
border: 1px solid red;
padding: 10px;
}
const box = document.getElementById('box');
box.classList.add('box')
# 将 DOM “离线”
回流和重绘都发生在当前被操作的 DOM 在页面上,如果我们在操作前先把这个 DOM 从页面下掉,等操作完再将这个 DOM 显示到页面上,这样也能减少回流和重绘制。但是因为涉及到 DOM 的显示和隐藏,肯定也会发生回流,所以这种方式并不适合频繁操作 DOM 的情况。
const box = document.getElementById('box');
box.style.display = 'none';
box.style.width = '200px';
box.style.height = '200px';
box.style.color = 'orange';
box.style.display = 'block';
现代浏览器都比较智能,很多之前需要使用的优化目前不用也是没问题的。不过为了使项目在不同的浏览器都能获得不错的运行效果,能优化的优化下挺好的,毕竟我们要精益求精嘛。