浏览器解析并渲染页面的过程
渲染过程
- 浏览器获取到 html 资源后开始解析 html (dom tree)
- 解析到 css 后根据 css 生成 css 规则树 (style rules)
- 在 dom 树和 css 规则树都生成完后,通过 dom 树和 css 规则树生成渲染树( render tree )
- 渲染树构建完成后,浏览器开始计算元素的大小和位置( layout )
- 根据计算好的节点信息将内容绘制到屏幕上( painting )
渲染的过程如下图:
渲染过程详解
1. dom 树和 css 规则树的先后顺序
浏览器获取的 html 资源后自上而下开始解析,生成 dom 树;如果遇到 style 的内联样式和 link 会将 css 交由 css 渲染器构建 css 规则树; 其中加载 link 引入的外部样式文件是异步加载的,并且整个 css 规则树也是与 dom 树的构建是并行的;
2. Javascript脚本的加载
由于 js 可以操作 dom,所以在 html 解析到 Javascript 脚本时会停止对 dom 的解析;而 css 的解析会阻塞 js 的执行,所以在某些情况下 css 的解析会导致 dom 的解析;
解决 js 阻塞 dom 解析的方法:
// 1. 给 script 标签添加 defer 属性,适用于 js 中涉及到 dom 的操作,或者与其他的 js 有相互引用的关系
// 添加 defer 属性会将 Javascript 脚本延迟执行,但是 html 解析过程中遇到 script 标签仍然会进行下载
// defer 脚本会在 dom 解析完成后,DOMContentLoaded事件调用前执行,而且 defer 属性的 js 脚本会按照顺序执行,即在 dom 解析完成后会依次执行 test1、test2;
<script src="./test1.js" defer></script>
<script src="./test2.js" defer></script>
// 2. 给 script 标签添加 async 属性,适用于 js 与 dom 和其他 js 文件无关的情况
// 添加 defer 属性会将 Javascript 脚本异步执行,html 解析过程中遇到 script 标签会进行下载,且下载完成后立即进行异步执行
// 由于是异步执行,所以当有多个异步的 js 脚本时无法控制先后执行的顺序,所以有多个异步 js 时,这些 js 不要互相有引用
// 由于添加 async 属性,使 js 在下载完成后立即执行,所以为了在该 js 中不要进行 dom 的相关操作
<script src="./test1.js" async></script>
MDN:当初始的 HTML 文档被完全加载和解析完成之后,
DOMContentLoaded
事件被触发,而无需等待样式表、图像和子框架的完全加载。
3. 渲染树( render tree )的构建过程
- 从 dom 树的根节点开始遍历每一个可见的节点;例如引用 css 文件的 link 标签、引入js 文件的 script 标签和 css 属性设置了 display: none; 的节点
- 从 css 规则树 (style rules) 中找到对应的节点,然后根据节点信息和样式组合成渲染树( render tree )
4. 回流和重绘
根据开头描述的浏览器渲染过程中的 4、5 可以引入回流和重绘的概念,因为在页面构建时就会触发一次回流和重绘;
回流:当某个元素的大小、位置发生了变化时,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树;
重绘:当某个元素的字体颜色等不影响布局的属性变化时,不需要重新构建渲染树,只需直接重新绘制页面即可;
PS:
想要了解更多关于回流和重绘的内容,可以看我这一篇文章 怎样减少页面的回流和重绘