在浏览器中HTML
、CSS
和JavaScript
的使用都会使页面进行「解析」和「加载」。
解析
浏览器在解析HTML
的时候会将整个文档解析为一个DOM
树,也就是DOMTree
。DOMTree
遵循「深度优先原则」,比如下面👇的文档结构。
<!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>
<div>
<ul>
<li>
<a href=""></a>
<span style="display: none"></span>
<img src="" alt="" />
</li>
</ul>
</div>
</body>
</html>
<script>
// TODO
</script>
同样的,CSS
在解析的时候也会创建一个样式结构树,也就是CSSTree
。
最后DOMTree+CSSTree
结合形成renderTree
也就是渲染树,然后浏览器才用renderTree
开始渲染页面。
:::info
renderTree
的渲染过程:
- 渲染树每个节点都有自己的样式
- 渲染树不包含隐藏节点(
**display: none;**
) **visibility: hidden;**
是会进行页面绘制的,只是页面不可见- 渲染树上的每个节点都会被当作是一个盒子
:::
加载
DOMTree
在渲染期间是不会进行加载操作的(先有解析后又加载,解析和加载是异步完成的)。
解析只是把img
放到DOMTree
上,然后才开始加载资源。
回流(重排)/重绘
:::info
回流(重排):当使用JavaScript
对页面上的节点进行大小、布局、显示隐藏操作时就会产生「回流」和「重绘」。
重绘:除开「重排」外对节点进行更改背景色、字体色等就会引起重绘。
⚠️ 回流一定会引起重绘,而重绘不一定会引起回流(也就是说重绘可以单独产生),回流比重绘的代价要大的多!!!
一个页面至少有一次回流+重绘(也就是初始化的时候)。
回流时浏览器会重新构建受影响的部分(或全部)renderTree
,这个时候一定会引起重绘。
:::
下面详细的说明会引起回流的操作:
- 对
DOM
的增删 - 对
DOM
节点的位置变化 - 对元素
margin
、padding
、width
、height
、border
的更改 - 元素设置
display: block/none;
(visibility: hidden;
会引起重绘) - 页面初始化渲染
- 浏览器窗口尺寸的变化(缩放浏览器窗口)
- 操作元素
offset
、scroll
、client
、getComputedStyle
除开以上都会引起重绘。
如何减少回流+重绘
假如我们用JS
操作元素的样式:
var oBox = document.getElementById("box");
var boxStyle = oBox.style;
boxStyle.width = "100px"; // 回流+重绘
boxStyle.height = "100px"; // 回流+重绘
boxStyle.margin = "10px"; // 回流+重绘
boxStyle.padding = "10px"; // 回流+重绘
boxStyle.backgroundColor = "black"; // 重绘
boxStyle.border = "1px solid red"; // 回流+重绘
boxStyle.color = "#fff"; // 重绘
boxStyle.fontSize = "32px"; // 回流+重绘
var h1 = document.createElement("h1");
h1.innerHTML = "我是一个h1";
// appendChild 只会重绘+回流 h1 自己,如果插入到某元素之前,那么 h1 后面所有的元素都需要回流+重绘
document.body.appendChild(h1);
以上代码可以看到当我们频繁的给oBox
设置样式就会频繁的引起回流和重绘,那么如何进行优化呢?
1、利用display: none;
的属性
var oBox = document.getElementById("box");
var boxStyle = oBox.style;
boxStyle.display = "none"; // 回流+重绘
boxStyle.width = "100px";
boxStyle.height = "100px";
boxStyle.margin = "10px";
boxStyle.padding = "10px";
boxStyle.backgroundColor = "black";
boxStyle.border = "1px solid red";
boxStyle.color = "#fff";
boxStyle.fontSize = "32px";
boxStyle.display = "block"; // 回流+重绘
2、给元素设置类名
// 在 css 中定义好 .active
// .active{...}
var oBox = document.getElementById("box");
oBox.className += " active"; // 这样只会引起一次回流+重绘
3、利用style.cssText
属性
这样的方式适用于动态值。
var oBox = document.getElementById("box");
oBox.style.cssText = `width: ${200}px; height: 100px; background-color: green`;
4、创建文档碎片
var frag = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
frag.appendChild(document.createElement("li"));
}
document.body.appendChild(frag);
5、缓存数据
var oBox = document.getElementById("box");
var offset = oBox.offsetWidth;
div.style.width = offset + 10 + "px";