渲染引擎
一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块
- HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树
- CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施
- Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果
- 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型
- 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果
渲染整体过程
- 构建 DOM 树:用 HTML 分析器,解析 HTML 元素,构建一棵 DOM 树;
- 构建 CSSOM 树:用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,构建出一棵 CSSOM 树;
- 若在构建 DOM 树的过程中,当 HTML 解析器遇到一个 script 标记时,即遇到了 JS,将立即阻塞 DOM 树的构建,将控制权移交给 JS 引擎,等到 JS 引擎运行完毕,浏览器才会从中断的地方恢复 DOM 树的构建;
- 构建 Render 树:将 DOM 树和 CSSOM 树关联起来,构建一棵 Render 树;
- 确定节点坐标:根据 Render 树结构,为每个 Render 树上的节点计算确定一个在显示屏上出现的精确坐标;
- 绘制页面:根据 Render 树和节点显示坐标,然后调用每个节点的 paint 方法,将它们绘制出来。
阻塞渲染的分类
- CSS阻塞渲染
在构建渲染树时,需要完备的DOM树,CSSOM树,而CSSOM的解析可能会阻塞DOM解析,或者CSSOM树的未完成会阻塞渲染树的构建,这就是CSS阻塞渲染。 - JS阻塞渲染
JS 可以查询和修改 DOM 与 CSSOM,所以当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JS 引擎, HTML 解析器会等待 JS 引擎运行完毕,这就是所谓的JS阻塞渲染。
CSS阻塞渲染的情况
阻塞 DOM 解析(CSSOM 树的构建通过阻塞 JS 代码而阻塞 DOM 的解析)
当在 JavaScript 中访问了某个元素的样式,那么这时候就需要 等待这个样式被下载 完成才能继续往下执行,所以在这种情况下(从某种角度看),CSS 也会阻塞 DOM 的解析。
阻塞 DOM 解析(CSSOM 树的构建通过阻塞 JS 代码而阻塞 DOM 的解析)
<body>
<h1>red1</h1>
<link rel="stylesheet" href="https://www.youtube.com/file.css"><!--这里会卡一段时间-->
<h1>red2</h1>
</body>
- 可以看到页面依旧处于白屏状态
- 点击浏览器的停止加载按钮,red1内容会被渲染出来,此时查看Dom树,发现是没有
<h1>red2</h1>这个节点的 - 当资源下载失败时,
<h1>red2</h1>这个DOM节点才会被解析到,然后渲染出来
CSS阻塞渲染解决办法
CSS引入的位置 —— 针对阻塞 DOM 解析
一般我们把<style>、<link>放在<head>里面,提前加载好CSS资源,这样当 JavaScript 请求到样式表时将不必等待 CSS 资源的加载
媒体查询的方式 —— 针对阻塞 render 树的构建
<!-- 适用于所有情况,始终阻塞渲染 -->
<link href="style.css" rel="stylesheet">
<!-- 网页首次加载时,只在打印内容时适用 -->
<link href="print.css" rel="stylesheet" media="print">
<!-- 如果不是在打印内容时,该样式表不阻塞渲染 -->
<!-- 符合条件时浏览器将阻塞渲染,直至样式表下载并处理完毕 -->
<link href="other.css" rel="stylesheet" media="(max-width: 400px)">
<!-- 如果不满足条件,不会阻塞渲染,但依旧会请求下载对应的资源 -->
有些 CSS 资源在首次渲染中可能不用用到,只是在用户交互(比如改变页面大小)时才会用到,所以我们通过媒体查询的方式来判断是否需要在首次渲染加载。这样从某种程度上会减少首屏加载时间。
JS阻塞渲染
JS阻塞渲染与CSS阻塞渲染的最大区别在于css的解析是可预测的,而JS阻塞渲染是不可预测的,因为JS可能随时修改DOM节点,乃至动态加载。
-
内联脚本阻塞渲染
<body> <h1>AAA</h1> <script> let d = Date.now() while (Date.now() < d + 1000 * 3) { } </script> <h2>BBB</h2> </body>- 刚加载时页面白屏,3秒后才会渲染出内容
- 说明内联JS会阻塞 DOM 解析和渲染,并且会一直阻塞
-
外联同步脚本阻塞渲染
<body> <h1>AAA</h1> <script src="./test.js"></script> <h2>BBB</h2> </body>// test.js let d = Date.now() while (Date.now() < d + 1000 * 3) { }- 可以看到一开始就会渲染出 AAA,3s 后才渲染出 BBB
- 说明外联脚本也会阻塞DOM解析与渲染,但是因为无法确定脚本中的内容,所以会优先渲染一次已经构建DOM,确保加载的脚本能取得最新的DOM
如何解决JS阻塞渲染
-
<script>引入的位置- 如果页面渲染内容为
<script>标签请求的内容,则该<script>标签一般需要放在<head>里面 - 如果页面渲染内容跟
<script>标签内容无关的话,比如说 DOM 事件、加载其他(还未见的)内容,则该<script>标签一般放在<body>标签里的最后位置
- 如果页面渲染内容为
-
defer 和 async 属性
- 如果需要某段 JavaScript 代码需要提前加载,即可能会放在里面或某些 DOM 节点前面,则给
<script>标签添加 defer 或 async 属性:如果加载完需要 立刻执行 则使用 async 属性;如果加载完不需要立刻执行,想要在页面结构加载完(window.onload)再立刻执行的话,使用 defer 属性;
- 如果需要某段 JavaScript 代码需要提前加载,即可能会放在里面或某些 DOM 节点前面,则给
-
值得注意的是:
- 没有使用这两种属性之一的话,则 JavaScript 的加载和运行都会阻塞渲染
- 使用这两种属性之一的话,则 JavaScript 的加载不会阻塞渲染,但运行仍会阻塞渲染
<body> <h1>AAA</h1> <script defer/async src="./test.js"></script> <h2>BBB</h2> </body>
参考资料:https://juejin.cn/post/6951741020866347044#heading-17
本文探讨了浏览器渲染过程中CSS和JS如何阻塞渲染,包括CSS阻塞DOM解析和render树构建,JS阻塞DOM解析和渲染。提出了解决方案,如调整CSS引入位置,使用媒体查询,以及利用defer和async属性减少JS阻塞。

1249

被折叠的 条评论
为什么被折叠?



