您的位置:首页 > 其它

从CRP(关键渲染路径)优化中谈浏览器渲染原理

2017-08-29 01:55 218 查看

前言:

该文中涉及的一切问题,只在页面首次渲染条件下讨论,因后续产生的DOM和CSSOM的更新及布局绘制不在本文范围内;

本文参考Google的Critical Rendering Path部分,做了选择性的摘录,以及自己的思考和总结,若对原文感兴趣的可以点击前往。如有表述不当的地方,还请指出

1. CRP

CRP,即critical rendering path的缩写,中文译文关键渲染路径。具体是什么呢?

浏览器从收到 HTML、CSS 和 JavaScript 字节到对其进行必需的处理,从而将它们转变成渲染的像素这一过程中有一些中间步骤,这些必要的步骤,就是CRP。

注意:

CRP的起点发生在新页面,为从服务器获得HTML文件的那一刻;故以下几项和CRP没有半毛钱全系: 通过控制台添加节点、hover, active等使样式发生变化、用JS写成的交互性渲染操作之类的操作。

CRP的终点是完成浏览器内像素的渲染,但之后的工作不属于CRP: 例如 1. 不会对页面的初始渲染产生影响的js代码的运行;2. 与服务器进行的后续数据传输;3. 甚至不符合当前的媒体查询条件的CSS文件的加载(因为这些样式对当前来说是不起作用的,不符合“必需”要求);

问题来了,凭什么说例子中的几项不在CRP中,即:如何判别某一事项不会影响页面的初次渲染成像呢?那就需要谈及浏览器的渲染流程。

2. 浏览器的渲染原理

2.1 对象模型(OM)的构建

2.1.1 文档对象模型(DOM)

虽然家喻户晓,但还是说一下吧:

<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>


一串HTML/SVG/XHTML等标记语言的代码传入浏览器后,会经过如下步骤:



字节 -> 字符 -> 令牌(标记) -> 节点 -> 文档对象模型

link 和 script 也是属于DOM的,它们也是HTML元素,也是节点

2.1.2 CSS对象模型(CSSOM)

当在读取HTML遇到linkstyle时,会返回相应的CSS代码,例如:

body { font-size: 16px }

p { font-weight: bold }

span { color: red }

p span { display: none }


此时浏览器亦会参考DOM的流程执行以下步骤:



注意这并非实际的最终样式表,因为每个浏览器会有自身的默认样式(User Agent)

2.2 渲染树创建过程中的阻塞

2.2.1 CSS为阻塞资源

渲染树由DOM和CSSOM共同构建,故意味着浏览器在将CSSOM构建完成前,不会完成渲染树的构建: CSS被视为阻塞渲染的资源

注意:当css资源不符合当前媒体查询条件时,可不被视为阻塞资源,即该CSS资源不参与CRP,为非必要的

<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">


print.css 和 other.css 只有在打印模式/页面宽度大于40em时才会参与CSSOM的构建,但仍然会被浏览器下载

以下为纯HTML 和 掺杂CSS阻塞的HTML 的渲染流程的区别

1. 纯HTML



2. 只含CSS



2.2.2 JavaScript的阻塞

JavaScript, HTML和CSS之间有着以下依赖关系

1. 当构建DOM树时,若遇到
<script>
标签,则会立刻停止DOM树的构建,而选择将
<script>
内的脚本运行完后再构建DOM:

<!--代码一-->
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path: Script</title>
<script>
var span = document.getElementsByTagName('span')[0];
span.innerHTML = 'Welcome';
</script>
</head>
<body>
<span>Hello World!</span>
</body>
</html>


该例子是个经典例子:浏览器运行脚本代码,但由于JavaScript脚本阻断了DOM的构建,故
<span>
标签此时并没有在DOM中。解决方法就是将
<script><
c5c1
/code>标签整体下移至紧贴</body>
之上(想我当初第一份实习工作时就犯过这种低级错误)。

那么,又有人问了,如果不是内联脚本,而是外联引入脚本呢。那么恭喜你,你得到了一个更糟糕的结果:

DOM的构建仍然被暂停以等待JavaScript脚本运行完,而且还需要将从服务器获得外联脚本的时间计算在内。

如果恰巧遇到网络波动或JS文件过大导致JavaScript引入过程过慢,则DOM构建将会严重滞后,出现常见的网页白屏卡住现象。

2. 若需要JavaScript脚本运行时,CSSOM也在构建,由于JavaScript也许会有修改CSS的操作,故JavaScript的运行会被浏览器延时执行并等待CSSOM的构建完毕:

<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="app.js"></script>
</body>
</html>


该代码中同时存在CSS, HTML 和 JavaScript, 渲染过程如下



注意T1和T2之间的状态为block,即浏览器进入阻塞状态(正常情况下应该运行JavaScript代码),等待CSSOM的构建完成。

总结就是:JavaScript会阻塞DOM树的创建,若JavaScript需要运行时CSSOM也在创建,则CSSOM的构建流程将插入到JavaScript之前,进一步阻塞DOM的创建。

想避免无关初次渲染相关的JavaScript脚本的影响其实很简单,在你确定相应的JS脚本和初次渲染无关时,可以在
<script>
标签内添加
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 performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
<script src="app.js" async></script>
</body>
</html>


此时的渲染流程如下:



向 script 标记添加异步关键字可以指示浏览器在等待脚本可用期间不阻止 DOM 构建,这样可以显著提升性能。

注意,只是在等待脚本传输时的时间DOM可以被允许继续创建,当脚本内容从服务器传输完成时,若此时DOM的构建仍未完成,浏览器仍将暂停DOM的构建(进入阻塞),以完成JavaScript代码的完成。

由于本例子的代码极其短小,故构建DOM和CSSOM的时间极短,而网络传输所用时间相比来说要长的多,故在JS脚本的等待期内,DOM和CSSOM均已构建完毕。而JS脚本内无干扰渲染的操作(将某段脚本标记为异步的前提条件),故在JS脚本运行完后DOM和CSSOM直接合成渲染树。

2.3 渲染树构建,布局,绘图

渲染树(render tree) 的创建是DOM和CSSOM结合的过程。

渲染树最终渲染的元素和DOM中的元素不尽相同:若某元素带有
display: none
的样式,则该元素不在渲染树中;不过,
visibility:hidden
作用的元素虽然不显示但仍然占位,故存在于渲染树中

渲染树的构建如下



当构建完渲染树时,意味着让浏览器显示内容的图纸已经做好了,接下来就是布局(layout)阶段以及绘制(paint)阶段

布局(layout): 布局计算每个对象的精确位置和大小。布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸:所有相对测量值都转换为屏幕上的绝对像素。

布局(layout):最后,既然我们知道了哪些节点可见、它们的计算样式以及几何信息,我们终于可以将这些信息传递给最后一个阶段:将渲染树中的每个节点转换成屏幕上的实际像素。这一步通常称为“绘制”或“栅格化”。

3. CRP的优化

下面简要概述了浏览器完成的步骤:

处理 HTML 标记并构建 DOM 树。

处理 CSS 标记并构建 CSSOM 树。

将 DOM 与 CSSOM 合并成一个渲染树。

根据渲染树来布局,以计算每个节点的几何信息。

将各个节点绘制到屏幕上。

优化关键渲染路径,就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间

为尽快完成首次渲染,我们需要最大限度减小以下三种可变因素:

关键资源的数量。

关键路径长度。

关键字节的数量。

关键资源是可能阻止网页首次渲染的资源。这些资源越少,浏览器的工作量就越小,对 CPU 以及其他资源的占用也就越少。

同样,关键路径长度受所有关键资源与其字节大小之间依赖关系图的影响:某些资源只能在上一资源处理完毕之后才能开始下载,并且资源越大,下载所需的往返次数就越多。

最后,浏览器需要下载的关键字节越少,处理内容并让其出现在屏幕上的速度就越快。要减少字节数,我们可以减少资源数(将它们删除或设为非关键资源),此外还要压缩和优化各项资源,确保最大限度减小传送大小。

优化关键渲染路径的常规步骤如下:

对关键路径进行分析和特性描述:资源数、字节数、长度。

最大限度减少关键资源的数量:删除它们,延迟它们的下载,将它们标记为异步等。

优化关键字节数以缩短下载时间(往返次数)。

优化其余关键资源的加载顺序:您需要尽早下载所有关键资产,以缩短关键路径长度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息