【青训营】- 浏览器的渲染过程

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

前几篇讲了关于url和请求的一些过程,今天来学习以下浏览器在渲染页面时的过程。

一、概括

浏览器的渲染流程可以概括成下列步骤:

  1. 读取HTML文件构建 DOM树(DOM tree)CSSDOM树(CSSDOM tree)
  2. 遇到 JS文件则阻塞 DOM树CSSDOM树 的构建,优先加载 JS文件
  3. 构建 渲染树(render tree),并渲染到页面上。
  4. 渲染树节点发生改变时,进行页面的 重绘(repaint)回流

二、详解

1. 构建 DOM树 和 CSSDOM树

HTML文档中有着许许多多的节点,如:文档节点、元素节点、文本节点、属性节点、注释节点等。HTML文档是自上而下解析的。 浏览器读取HTML文件后,会经过以下流程:
读取文档(字节) -> 转为字符 -> 变成标签 -> 转为节点 -> 构建 DOM树

<html>
<head>
    <meta charset="utf-8" />
    <link href="./asdf.js" />
    <script src="https://afsadfljaslj.js"></script>
</head>
<body>
    <div>
        <h1>Hello World</h1>
    </div>
</body>
</html>
复制代码

123.png

CSSDOM树 的构建与 DOM树的构建类似,因为CSS文档中,所有元素也都是节点,且与HTML文档中的标签节点一一对应,各个节点中也有着层级关系,如兄弟,父子,子孙等。构成 CSSDOM树。流程为:
读取文档(字节) -> 转为字符 -> 变成标签 -> 转为节点 -> 构建 CSSDOM树

2. JS加载和阻塞

当遇到JS文件时,会阻塞 DOM树 和 CSSDOM树 的构建,优先加载 JS文件,是因为浏览器认为如果同时进行对 DOM树 的构建的话,若 JS 中存在对 DOM进行操作的代码的话,则浪费了资源。如:

<html>
<head>
    <meta charset="utf-8" />
    <link href="./asdf.js" />
    <script src="./script.js"></script>
</head>
<body>
    <div>
        <h1>Hello World</h1>
    </div>
</body>
</html>
复制代码
console.log(123);
document.querySelect("h1").innerText = "你好";
复制代码

如果加载一个体量过大的 JS文件,可能需要用到 1秒、2秒,甚至5秒的可能,那么对 DOM树 和 CSSDOM树 的构建则就耽误了,页面没能及时渲染到浏览器上,用户体验感极差。
对此有两种解决方法:

  • 因为 HTML文档 是自上而下解析的,所以将 <script>标签放在文档最底下(适用于内部文件)
  • 添加 asyncdefer 属性进行异步加载 JS文件(适用于外部文件)

关于 asnycdefer:

  • async:(适用于 JS文件 不关心页面 DOM元素 时)
    • 脚本会在加载完毕后执行。async脚本的加载不计入DOMContentLoaded事件统计。
  • defer:(适用于 JS文件 较关心页面 DOM元素 时)
    • 文档解析时,遇到设置了defer的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析&渲染完毕后。会等到所有的defer脚本加载完毕并按照顺序执行,执行完毕后会触发DOMContentLoaded事件。
  • 不知道的情况下,用 defer 总会安全点。

3. 构建渲染树

渲染树 有 DOM树 和 CSSDOM树 合并而成,三者的构建顺序并无先后,而是并行构建的,形成一边解析一边构建一边渲染的情况,所以有时我们会看到页面渲染出来了一半,另一半还是空白的情况。

4. 重绘和回流

当渲染树中的某个节点的可视化属性(看得见的改变)发生了改变,则会引发页面的重绘和回流,两者的区别是:

  • 重绘(repaint)
    • 渲染树节点发生变化,但并不影响该节点在页面种的空间位置以及大小,如:背景颜色、字体颜色等,但是该节点的宽、高、内外边距并没有改变,此时触发浏览器的 重绘
  • 回流(reflow)
    • 也称为重排,当渲染树节点发生变化,且影响到了该节点的几何属性(如宽、高、内边距、外边距、或是float、position、display:none;等等),或导致节点发生了位置变化(如lefttopbottomright等),此时出发浏览器的 回流。需要重新构建渲染树,且该节点之后的所有节点都会发生改变,重新布局。

注意:回流必然引起重绘,而重绘比一定引起回流

一些以前回流的情况:

  1. 添加或者删除可见的DOM元素;
  2. 元素位置改变——display、float、position、overflow等等;
  3. 元素尺寸改变——边距、填充、边框、宽度和高度
  4. 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
  5. 页面渲染初始化;
  6. 浏览器窗口尺寸改变——resize事件发生时;

可见,回流 消耗的资源比 重绘 要大得多。

三、其他

1. 不同内核的浏览器的渲染流程

不同浏览器的渲染流程在细节上是不同的,如:

这是 webkit 的主要流程:

这是 Geoko 的主要流程:

虽然在一些地方有所不同,但渲染主流程还是一样的。

2. 如何减少和避免回流

  • CSS:
    • 避免设置多层的内联样式
    • 尽可能在 DOM树最末端 改变 class
    • 若要设置动画效果,最好脱离正常文档流
    • 避免使用 CSS表达式(如:calc())
    • visibility: hidden(渲染树上有保留) 代替 display: none(渲染树上会删除掉)
  • JS
    • 避免频繁操作 style,最好用一个class一次性更改样式
    • 避免频繁操作 DOM,必要的话使用 documentFragment
    • 可以使用“DOM离线操作”(将该元素设为 display:none,后操作结束再显示出来,这样只进行两次回流),但建议还是用第一个方法。

有什么问题希望大家可以在评论区指出,我及时纠正。

新人上路,还请多多包含。
我是MoonLight,一个初出茅庐的小前端。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享