声明:本文是对developers.google.com的critical-rendering-path部分的总结。大部分内容直接从critical-rendering-path摘抄过来,并非原创。
浏览器到底是如何使用我们的 HTML、CSS 和 JavaScript 在屏幕上渲染像素的呢?
第一步: 构建 Object Model
浏览器渲染页面前需要先构建 DOM 和 CSSOM 树。浏览器拿到服务器端的字节码时,进行以下步骤:
- 字节 → 字符 → 令牌 → 节点 → 对象模型。
- HTML 标记转换成文档对象模型 (DOM);CSS 标记转换成 CSS 对象模型 (CSSOM)。
- DOM 和 CSSOM 是独立的数据结构。
假设我们有下面这段HTML:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
浏览器会构建出下面的DOM:
DOM假设我们还有这么一段css:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
它会构建出这样的CSSOM:
CSSOMCSSOM 为何具有树结构?为页面上的任何对象计算最后一组样式时,浏览器都会先从适用于该节点的最通用规则开始(例如,如果该节点是 body 元素的子项,则应用所有 body 样式),然后通过应用更具体的规则(即规则“向下级联”)以递归方式优化计算的样式。
渲染树构建、布局及绘制
CSSOM 树和 DOM 树合并成渲染树,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上。
- DOM 树与 CSSOM 树合并后形成渲染树。
- 渲染树只包含渲染网页所需的节点。
- 布局计算每个对象的精确位置和大小。
- 最后一步是绘制,使用最终渲染树将像素渲染到屏幕上
阻塞渲染的 CSS
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。请务必精简您的 CSS,尽快提供它,并利用媒体类型和查询来解除对渲染的阻塞。
在渲染树构建中,我们看到关键渲染路径要求我们同时具有 DOM 和 CSSOM 才能构建渲染树。这会给性能造成严重影响:HTML 和 CSS 都是阻塞渲染的资源。
JavaScript对渲染的影响
除非将 JavaScript 显式声明为异步,否则它会阻止构建 DOM
我们的脚本在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JavaScript 引擎;等 JavaScript 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建。
换言之,我们的脚本块找不到网页中任何靠后的元素,因为它们尚未接受处理!或者,稍微换个说法:执行我们的内联脚本会阻止 DOM 构建,也就延缓了首次渲染。
无论我们使用 <script> 标记还是内联 JavaScript 代码段,您都可以期待两者能够以相同方式工作。 在两种情况下,浏览器都会先暂停并执行脚本,然后才会处理剩余文档。不过,如果是外部 JavaScript 文件,浏览器必须停下来,等待从磁盘、缓存或远程服务器获取脚本,这就可能给关键渲染路径增加数十至数千毫秒的延迟。
默认情况下,所有 JavaScript 都会阻止解析器。由于浏览器不了解脚本计划在页面上执行什么操作,它会作最坏的假设并阻止解析器。向浏览器传递脚本不需要在引用位置执行的信号既可以让浏览器继续构建 DOM,也能够让脚本在就绪后执行;例如,在从缓存或远程服务器获取文件后执行。
为此,我们可以将脚本标记为异步:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="app.js" async></script>
</body>
</html>
向 script 标记添加异步关键字可以指示浏览器在等待脚本可用期间不阻止 DOM 构建,这样可以显著提升性能。
网友评论