浏览器渲染之关键渲染路径

很多时候我们会通过压缩浏览器加载资源来使页面加载更快。但是实际的浏览器渲染页面的过程有时候却是模棱两可的。所以这篇文章就来说说关键渲染路径,也称为关键呈现路径(Critical Rendering Path),具体定义为:浏览器将 HTML、CSS 和 JavaScript 转换成实际运作的网站必须采取的一系列步骤。

首先我们来看一下浏览器关键渲染路径的过程。

首先浏览器获取到html文件后开始构建DOM树,获取css后开始构建CSSOM树,然后结合dom树和cssom树生成render树,再根据dom所在页面位置进行布局,最后在页面绘制像素呈现网页。在这个过程中默认加载的js会阻止网页呈现,由于js可以更改dom和对应css,所以这个过程在操作dom的js中会再次执行。

首先我们来说说dom树的构建过程。dom树构建步骤如下:

1.characters(标记)。浏览器会解析html文件中的标签,例如:<html>,<p>,</body>等;
2.tokens(令牌)。遇到标记后浏览器使用令牌生成器开始生成令牌,例如:<p>hello</p>这样一个文本标签会生成:StartTag:p, hello, EndTag:p这样3个令牌;
3.nodes(节点)。这一步是用来消耗令牌的。生成对应的树结构,我们使用下面的html文件来说明这个过程。

<html>
    <head></head>
    <body>
        <p>hello</p>
    </body>
</html>

上面对应的结构如下:

4.步骤3的结构解析完成后就形成了DOM,也就是文档对象模型,里面的内容包含了html的所有内容和属性,以及所有节点之间的关系。

值得说明的是,在生成令牌的同时也在消耗令牌,所以是逐渐构建的过程,并非需要等待所有令牌生成后在开始生成节点。

接下来我们再说说CSSOM(层叠样式表对象模型)。

其实css树构建的过程和dom树类似。也是经历了characters, tokens, nodes的过程最后形成cssom,不过这里的识别标记规则和dom不一样,并且css拥有继承属性的操作方式。父级样式会被子级样式继承。这里需要特殊说明的是css会阻塞页面的渲染和呈现,也就是浏览器需要等到css内容被处理后才会开始渲染。

在有了DOM和CSSOM后浏览器便开始形成渲染树,操作过程为复制DOM和对应的CSS属性到渲染树。

这里需要注意的是如果遇到不可见(display:none)属性,会跳过该元素和其子项。在html的head里不包含任何可见信息,所以这部分的内容会被很快的去除。我们用一个例子来表示这个过程。

html页面如下:

上图所示的html结构在生成render树的时候,首先由于cssom的继承特性,p和span标签都拥有了16px的字号,但是由于span为display:none,所以并不会在render的时候被引入render树中。所以最后的render tree结构为:

接下来我们说说js的执行。

js的执行过程发生在当页面遇到js代码的时候,此时会阻止页面文档的解析直到执行完毕。这里值得注意的是:

js在执行时如果cssom未完成,会等待cssom完成后再开始解析;
如果存在多个js,第一个js加载的时候,浏览器并不会完成停止,此时会通过预加载扫描程序来发现css和js资源并同步下载;
由上面两点我们可以知道js执行时如果cssom未完成,那么会存在一段时间的block时间段,并且当一个js文件执行的时候浏览器也会并行下载其它资源,也就是不会出现等待一个load再load另一个的现象。

既然我们知道了渲染过程,那么我们该从哪几个方面来让页面更快的呈现呢?

1.优化关键渲染路径长度;
2.减少关键字节数;
3.减少关键资源数;
对于html,css,js我们都可以通过压缩和缓存来实现加载的更快速。这种方式都可以减少关键字节数。

对于路径长度,简单来说例如:一个网页只有一个外联css,那么此时的关键路径长度为2,也就是生成DOM,等待css加载后生成CSSOM,此时如果一个来回需要的时间为1s,那么这个网页就需要2s才能开始渲染,那么如果把css放在页面内部呢,此时就没有css加载的过程,关键路径长度为1,此时1个来回便可以渲染出页面,也就能更快的呈现页面。

同时我们可以通过一些方式来消除css和js的阻塞。

针对css,我们可以通过在link标签中使用媒体查询来取消阻止,例如:media=”print”,在渲染过程中只有在打印时才会加载其内部的css样式。同时也可以通过把css写入文档内来取消阻止。

针对js,我们可以延迟js的执行,此时可以添加defer属性。也可以使用anync属性来异步加,DOMContentLoaded事件的触发并不受async脚本加载的影响,也就不存在阻塞渲染的问题了。当然更多时候我们应该避免js对dom的过多操作来实现更好的加载,saync属性适合例如网页监控,打点等脚本中,此类脚本基本不操作页面dom,也就不会造成再次渲染的问题。

通过对关键渲染路径的分析,我们可以更加明确知道什么可以影响渲染页面,为什么这么做可以加快页面加载。

发表评论