为了把HTML、CSS和JavaScript转化成活灵活现、绚丽多彩的网页,浏览器需要处理一系列的中间过程,优化性能其实就是了解这个过程中发生了什么-即CRP(Critical Rendering Path,关键渲染路径)。首先,我们从头开始快速学习一下浏览器是如何显示一个简单网页的。
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Pathtitle>
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
body>
html>
一个普通的页面,里面包含一些文本和一张图片,浏览器是如何处理这个页面的呢?
当浏览器构建上述网页DOM的时候,在head里面碰到一个link标签,这个标签引用了一个外部的CSS样式表:style.css。浏览器预测会需要这个资源来渲染页面,因此会立即发出一个该资源的请求,该请求返回以下内容:
与HTML一样,我们需要将收到的 CSS 规则转换为浏览器可以理解、能够处理的东西。因此,我们重复与处理 HTML 非常相似的过程:
最终输出的是CSS对象模型,即CSSOM。
想要查看CSS处理过程所花费的时间,可以在录制的时间轴中查看Rendering事件中的Recalculate Style事件:与DOM解析不同,timeline不显示单独的“Parse CSS”条目,而是在Recalculate Style事件下一同捕获CSS解析、CSSOM构建以及computed styles的递归计算。
前面介绍了我们根据输入的HTML及CSS构建了DOM树和CSSOM树,但二者是独立的对象:DOM描述的是文档内容,CSSOM描述的是应用于文档的样式规则。浏览器会把DOM和CSSOM组合起来构建一个渲染树(Render-tree),渲染树会捕获页面上所有可见的DOM内容以及应用在每个节点上的CSSOM样式。
构建渲染树的过程大致如下:
1.从DOM树的根节点开始,遍历每个可见的节点
2.为每一个可见的节点匹配并应用对应的CSSOM规则
3.生成有内容和计算样式的可见节点
注意:小提示:注意visibility: hidden和display: none二者的区别。visibility: hidden只是让元素在视觉上不可见,但是元素在页面布局中仍然占据空间。而display: none则是从渲染树中删除某一个元素,不仅视觉上不可见,渲染树上也没有,更不会影响到页面的布局。
最终输出的就是一个包含了所有可见节点的内容及样式信息的渲染树。
到目前为止,我们已经计算出了哪些节点是可见的以及它们的计算样式,但是我们还没有计算它们在设备视口(viewport)中的准确位置及尺寸大小——这就是布局(Layout)阶段要做的工作,也就是常说的重排(reflow)。
为了计算出页面中每个对象的准确大小和位置,浏览器从渲染树的根节点开始遍历,计算页面上每个对象的几何信息。举例如下:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!title>
head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!div>
div>
body>
html>
上面页面的 body 包含两个嵌套 div:第一个 div(父元素)将节点尺寸大小设置为视口宽度的 50%,第二个 div 的宽度为父元素的 50%,即视口宽度的 25%!
布局过程的输出是一个“盒子模型”,它精确地捕获了每个元素在视口中的准确位置及尺寸大小:所有相对度量单位都被转换为屏幕上的绝对像素。
自此,我们已经知道了哪些节点是可见的以及它们的计算样式和几何信息,然后我们就可以把这些信息传送到最后一个阶段,即把渲染树中的每一个节点都转化到屏幕上实际的像素点。这个步骤通常被称为绘制(painting)或者栅格化(rasterizing)。
构建渲染树、布局与绘制所消耗的时间也可以通过timeline来查看:
现在回顾一下浏览器执行的几个步骤:
在构建渲染树部分我们已了解到:CRP要求DOM和CSSOM两者融合在一起才能构建渲染树。这就导致了一个性能问题:HTML和CSS都是阻塞渲染的资源。HTML很显然,没有DOM就没有内容去渲染。CSS没有那么明显,但确实是阻塞渲染的资源。我们知道一个正常的网页如果没有引入专用的css,页面有多丑陋。当我们的网页引入了专用的css,页面一加载出来的时候就是绚丽多彩的,如果css不阻塞渲染,我们看到的很可能是这样的一个画面:页面刚加载出来的时候其丑无比,过了一会,页面又变漂亮了……
既然CSS是阻塞渲染的资源,这就意味着在CSSOM构建完成之前,浏览器不会去渲染任何已处理的内容。要尽早、尽快地把CSS下载到客户端以优化首次渲染的时间。
使用CSS“媒体类型”和“媒体查询”优化阻塞渲染的CSS:
<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
小提示:「阻塞渲染」仅是指该资源是否会阻塞浏览器的首次页面渲染。无论 CSS 是否阻塞渲染,CSS 资源都会被下载,只是说非阻塞性资源的优先级比较低而已。
阻塞解析的JavaScript
js可以修改页面的内容、样式以及响应用户的交互,JS在DOM、CSSOM和JS执行之间引入了很多新的依赖关系,导致浏览器在处理和渲染页面上出现大幅延迟:
标签时,DOM构建会暂停,直到脚本执行完毕默认情况下,JavaScript 执行会阻塞解析器:当浏览器在文档中遇到标签时,DOM构建必须暂停,浏览器把控制权移交给JS引擎,JS引擎编译并执行脚本,脚本执行完毕后再继续构建DOM。
事实上,内联脚本始终会阻塞解析器,除非你编写额外的代码来延迟它们的执行。那通过引入的外联脚本呢?结果是一样的,浏览器都会暂停,然后执行脚本,脚本执行完毕之后再去处理文档的剩余部分。尽管如此,通过
引入外联脚本还是有一个很大的好处。
默认情况下,所有 JS 均会阻塞解析器,因为浏览器不知道脚本想在页面上做什么,因此它必须假定最糟的情况并阻塞解析器。但是,如果我们能够有个信号告知浏览器,说脚本无需在文档中引用它的确切位置被执行呢?这样一来,浏览器就会继续构建DOM,并在脚本准备就绪后执行脚本。
这个信号就是async——在script标签里面添加async关键字,其有两个特性:
script>
标签时不用阻塞DOM构建,因此浏览器会忽略脚本请求,继续解析DOM分析CRP性能
先定义三个用于描述CRP的词汇:
demo1:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critical Path: No Styletitle>
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
body>
html>
最简单的可用网页仅由 HTML 标记组成:无 CSS、javascript 或其他类型的资源。要呈现此网页,浏览器必须初始化请求、等待 HTML 文档准备就绪、对其进行解析、构建 DOM,最后使其呈现在屏幕上。
demo2:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
body>
html>
9KB关键字节
我们必须同时使用 HTML 和 CSS 来构建渲染树,因此 HTML 和 CSS 均为关键资源;浏览器需要一个网络往返过程来提取 HTML 文档,然后检索到的标记告知我们还需要 CSS 文件,这意味着,浏览器必须返回服务器并获取 CSS,然后才能在屏幕上呈现网页,因此,该网页最少需要两个往返过程才能显示(CSS 文件可能需要多个往返过程,重点在’最少’);两种资源加起来的关键字节总量最多为 9 KB。
demo3:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
<script src="app.js">script>
body>
html>
11KB关键字节
我们有三个关键资源,关键字节总量最多为 11 KB,但是关键路径长度仍然是两个往返过程,因为我们可以并行传输 CSS 和 JavaScript!
demo4: 如果app.js中的内容不涉及到操作DOM和CSSOM,只是一些分析类型的代码和其他不需要阻塞页面渲染的代码,则可以在中加入“async”属性:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
<script src="app.js" async>script>
body>
html>
异步执行脚本有以下几项优势:
因此,经过优化的网页恢复到了具有两个关键资源(HTML 和 CSS)、具有两个往返过程的最短关键路径长度和 9 KB 的关键字节总量。
demo5:
如果CSS样式表仅适用于打印:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet" media="print">
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
<script src="app.js" async>script>
body>
html>
因为 style.css 资源仅用于打印,所以只要DOM构建完成,浏览器就具有了渲染网页的足够信息! 所以,该网页仅具有一个关键资源(HTML),最小关键呈现路径长度为一个往返过程和5KB的关键字节。
常规步骤:
文章转载自https://segmentfault.com/a/1190000008550336