在浏览器中HTMLCSSJavaScript的使用都会使页面进行「解析」和「加载」。

解析

浏览器在解析HTML的时候会将整个文档解析为一个DOM树,也就是DOMTree
DOMTree遵循「深度优先原则」,比如下面👇的文档结构。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div>
  11. <ul>
  12. <li>
  13. <a href=""></a>
  14. <span style="display: none"></span>
  15. <img src="" alt="" />
  16. </li>
  17. </ul>
  18. </div>
  19. </body>
  20. </html>
  21. <script>
  22. // TODO
  23. </script>

DOM/CSS/渲染树、解析与加载、回流与重绘 - 图1

同样的,CSS在解析的时候也会创建一个样式结构树,也就是CSSTree

最后DOMTree+CSSTree结合形成renderTree也就是渲染树,然后浏览器才用renderTree开始渲染页面。 :::info renderTree的渲染过程:

  1. 渲染树每个节点都有自己的样式
  2. 渲染树不包含隐藏节点(**display: none;**
  3. **visibility: hidden;**是会进行页面绘制的,只是页面不可见
  4. 渲染树上的每个节点都会被当作是一个盒子 ::: WX20220705-103045.png
    WX20220705-102947.png

加载

DOMTree在渲染期间是不会进行加载操作的(先有解析后又加载,解析和加载是异步完成的)。
解析只是把img放到DOMTree上,然后才开始加载资源。

回流(重排)/重绘

:::info 回流(重排):当使用JavaScript对页面上的节点进行大小、布局、显示隐藏操作时就会产生「回流」和「重绘」。
重绘:除开「重排」外对节点进行更改背景色、字体色等就会引起重绘。

⚠️ 回流一定会引起重绘,而重绘不一定会引起回流(也就是说重绘可以单独产生),回流比重绘的代价要大的多!!!

一个页面至少有一次回流+重绘(也就是初始化的时候)。
回流时浏览器会重新构建受影响的部分(或全部)renderTree,这个时候一定会引起重绘。 :::

下面详细的说明会引起回流的操作:

  1. DOM的增删
  2. DOM节点的位置变化
  3. 对元素marginpaddingwidthheightborder的更改
  4. 元素设置display: block/none;visibility: hidden;会引起重绘)
  5. 页面初始化渲染
  6. 浏览器窗口尺寸的变化(缩放浏览器窗口)
  7. 操作元素offsetscrollclientgetComputedStyle

除开以上都会引起重绘。

如何减少回流+重绘

假如我们用JS操作元素的样式:

  1. var oBox = document.getElementById("box");
  2. var boxStyle = oBox.style;
  3. boxStyle.width = "100px"; // 回流+重绘
  4. boxStyle.height = "100px"; // 回流+重绘
  5. boxStyle.margin = "10px"; // 回流+重绘
  6. boxStyle.padding = "10px"; // 回流+重绘
  7. boxStyle.backgroundColor = "black"; // 重绘
  8. boxStyle.border = "1px solid red"; // 回流+重绘
  9. boxStyle.color = "#fff"; // 重绘
  10. boxStyle.fontSize = "32px"; // 回流+重绘
  11. var h1 = document.createElement("h1");
  12. h1.innerHTML = "我是一个h1";
  13. // appendChild 只会重绘+回流 h1 自己,如果插入到某元素之前,那么 h1 后面所有的元素都需要回流+重绘
  14. document.body.appendChild(h1);

以上代码可以看到当我们频繁的给oBox设置样式就会频繁的引起回流和重绘,那么如何进行优化呢?

1、利用display: none;的属性

  1. var oBox = document.getElementById("box");
  2. var boxStyle = oBox.style;
  3. boxStyle.display = "none"; // 回流+重绘
  4. boxStyle.width = "100px";
  5. boxStyle.height = "100px";
  6. boxStyle.margin = "10px";
  7. boxStyle.padding = "10px";
  8. boxStyle.backgroundColor = "black";
  9. boxStyle.border = "1px solid red";
  10. boxStyle.color = "#fff";
  11. boxStyle.fontSize = "32px";
  12. boxStyle.display = "block"; // 回流+重绘

2、给元素设置类名

  1. // 在 css 中定义好 .active
  2. // .active{...}
  3. var oBox = document.getElementById("box");
  4. oBox.className += " active"; // 这样只会引起一次回流+重绘

3、利用style.cssText属性
这样的方式适用于动态值。

  1. var oBox = document.getElementById("box");
  2. oBox.style.cssText = `width: ${200}px; height: 100px; background-color: green`;

4、创建文档碎片

  1. var frag = document.createDocumentFragment();
  2. for (let i = 0; i < 10; i++) {
  3. frag.appendChild(document.createElement("li"));
  4. }
  5. document.body.appendChild(frag);

5、缓存数据

  1. var oBox = document.getElementById("box");
  2. var offset = oBox.offsetWidth;
  3. div.style.width = offset + 10 + "px";