阅读 400

优化关键渲染路径

上个月,我写了一篇文章介绍什么是“关键渲染路径”,其实目的是为了给这篇文章做一个铺垫,本文将谈谈如何优化关键渲染路径(本文将假设您已经阅读过《关键渲染路径》这篇文章或已经懂得了什么是“关键渲染路径”)。

优化关键渲染路径可以提升网页的渲染速度,从而得到一个更好的用户体验。

如何优化关键渲染路径?

优化关键渲染路径有很多种方法与情况,不同情况下优化方式也各不相同,初步看起来这些优化方法五花八门,知识非常的零散。

但在这些看似零散的知识中,我们会发现一些规律,将这些规律总结起来后,可以得出一个结论:到目前为止,只有三种因素可以影响关键渲染路径的耗时。而所有的优化方式,都是在尽可能的针对这三种因素进行优化。

这三种因素分别是:

  • 关键资源的数量
  • 关键路径的长度
  • 关键字节的数量

切记,非常重要,所有优化关键渲染路径的方法,都是在优化以上三种因素。因为只有这三种因素可以影响关键渲染路径。

关键资源指的是那些可以阻塞页面首次渲染的资源。例如JavaScript、CSS都是可以阻塞关键渲染路径的资源,这些资源就属于“关键资源”。关键资源的数量越少,浏览器处理渲染的工作量就越少,同时CPU及其他资源的占用也越少。

关键路径中的每一步耗时越长,由于阻塞会导致渲染路径的整体耗时变长。关键路径的长度指的是关键渲染路径的总耗时。关键渲染路径的长度会受到很多因素的影响。例如:关键资源的网络情况、关键资源的数量、关键资源的字节大小、关键资源的依赖关系等。

关键字节的数量指的是关键资源的字节大小,浏览器要下载的资源字节越小,则下载速度与处理资源的速度都会更快。通常很多优化方法都是针对关键字节的数量进行优化。例如:压缩。

优化DOM

在关键渲染路径中,构建渲染树(Render Tree)的第一步是构建DOM,所以我们先讨论如何让构建DOM的速度变得更快。

HTML文件的尺寸应该尽可能的小,目的是为了让客户端尽可能早的接收到完整的HTML。通常HTML中有很多冗余的字符,例如:JS注释、CSS注释、HTML注释、空格、换行。更糟糕的情况是我见过很多生产环境中的HTML里面包含了很多废弃代码,这可能是因为随着时间的推移,项目越来越大,由于种种原因从历史遗留下来的问题,不过不管怎么说,这都是很糟糕的。对于生产环境的HTML来说,应该删除一切无用的代码,尽可能保证HTML文件精简。

总结起来有三种方式可以优化HTML:缩小文件的尺寸(Minify)使用gzip压缩(Compress)使用缓存(HTTP Cache)

缩小文件的尺寸(Minify)会删除注释、空格与换行等无用的文本。

本质上,优化DOM其实是在尽可能的减小关键路径的长度与关键字节的数量

优化CSSOM

与优化DOM类似,CSS文件也需要让文件尽可能的小,或者说所有文本资源都需要。CSS文件应该删除未使用的样式、缩小文件的尺寸(Minify)、使用gzip压缩(Compress)、使用缓存(HTTP Cache)。

除了上面提到的优化策略,CSS还有一个可以影响性能的因素是:CSS会阻塞关键渲染路径

CSS是关键资源,它会阻塞关键渲染路径也并不奇怪,但通常并不是所有的CSS资源都那么的『关键』。

举个例子:一些响应式CSS只在屏幕宽度符合条件时才会生效,还有一些CSS只在打印页面时才生效。这些CSS在不符合条件时,是不会生效的,所以我们为什么要让浏览器等待我们并不需要的CSS资源呢?

针对这种情况,我们应该让这些非关键的CSS资源不阻塞渲染

实现这一目的非常简单,我们只需要将不阻塞渲染的CSS移动到单独的文件里。例如我们将打印相关的CSS移动到print.css,然后我们在HTML中引入CSS时,添加媒体查询属性print,代码如下:

<link href="print.css" rel="stylesheet" media="print">
复制代码

上面代码添加了media="print"属性,所以上面CSS资源仅用于打印。添加了媒体查询属性后,浏览器依然会下载该资源,但如果条件不符合,那么它就不再阻塞渲染,也就是变成了非阻塞的CSS

我们可以写个DEMO测试一下:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    Hello
</body>
</html>
复制代码

上面代码使用Chrome开发者工具的性能面板捕获后的性能图如下:

阻塞的CSS资源性能捕获图

从上图中的首次绘制(First Paint)时间是在1200ms的位置,可以看到这个时间是浏览器加载CSS完毕后,而且可以看到Network栏中CSS显示Highest代表高优先级。

添加了媒体查询语句后,捕获出来的性能图如下:

非阻塞的CSS资源性能捕获图

首次绘制时间在不到100ms的位置,和domcontentloaded事件差不多的时间触发。同时CSS资源变成了Lowest,表示低优先级。

可以看到,浏览器依然会下载该CSS资源,但它不再阻塞渲染。

上面提供的方法是针对那些不需要生效的CSS资源,如果CSS资源需要在当前页面生效,只是不需要在首屏渲染时生效,那么为了更快的首屏渲染速度,我们可以将这些CSS也设置成非关键资源。只是我们需要一些比较hack的方式来实现这个需求:

<link href="style.css" rel="stylesheet" media="print" onload="this.media='all'">
复制代码

上面代码先把媒体查询属性设置成print,将这个资源设置成非阻塞的资源。然后等这个资源加载完毕后再将媒体查询属性设置成all让它立即对当前页面生效。

通过这样的方式,我们既可以让这个资源不阻塞关键渲染路径,还可以让它加载完毕后对当前页面生效。

类似的方案有很多,代码如下:

<link rel="preload" href="style.css" as="style" onload="this.rel='stylesheet'">

<link rel="alternate stylesheet" href="style.css" onload="this.rel='stylesheet'">
复制代码

上面两种方式都能实现同样的效果。

关于CSS的加载有这么多门道,到底怎样才是最佳实践?答案是:Critical CSS

Critical CSS的意思是:把首屏渲染需要使用的CSS通过style标签内嵌到head标签中,其余CSS资源使用异步的方式非阻塞加载。

CSS资源在构建渲染树时,会阻塞JavaScript,所以我们应该保证所有与首屏渲染无关的CSS资源都应该被标记为非关键资源。

所以Critical CSS从两个方面解决了性能问题:

  1. 减少关键资源的数量(将所有与首屏渲染无关的CSS使用异步非阻塞加载)
  2. 减少关键路径的长度(将首屏渲染需要的CSS直接内嵌到head标签中,移除了网络请求的时间)。

避免使用@import

大家应该都知道要避免使用@import加载CSS,实际工作中我们也不会这样去加载CSS,但这到底是为什么呢?

这是因为使用@import加载CSS会增加额外的关键路径长度。举个例子:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="http://127.0.0.1:8887/style.css">
    <link rel="stylesheet" href="https://lib.baomitu.com/CSS-Mint/2.0.6/css-mint.min.css">
</head>
<body>
    <div class="cm-alert">Default alert</div>
</body>
</html>
复制代码

上面这段代码使用link标签加载了两个CSS资源。这两个CSS资源是并行下载的。我们使用Chrome开发者工具的Performance面板捕获出的结果如下图所示:

使用link标签加载样式

从图中用红色方框圈出来的位置可以看出两个CSS是并行加载的,首次绘制时间取决于CSS加载时间较长的资源加载时间。

现在我们改为使用@import加载资源,代码如下:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="http://127.0.0.1:8887/style.css">
</head>
<body>
    <div class="cm-alert">Default alert</div>
</body>
</html>
复制代码
/* style.css */
@import url('https://lib.baomitu.com/CSS-Mint/2.0.6/css-mint.min.css');
body{background:red;}
复制代码

代码中使用link标签加载一个CSS,然后在CSS文件中使用@import加载另一个CSS。使用Chrome开发者工具再次捕获出的结果如下图所示:

使用@import加载CSS

可以看到两个CSS变成了串行加载,前一个CSS加载完后再去下载使用@import导入的CSS资源。这无疑会导致加载资源的总时间变长。从上图可以看出,首次绘制时间等于两个CSS资源加载时间的总和。

所以避免使用@import是为了降低关键路径的长度。

异步JavaScript

所有文本资源都应该让文件尽可能的小,JavaScript也不例外,它也需要删除未使用的代码、缩小文件的尺寸(Minify)、使用gzip压缩(Compress)、使用缓存(HTTP Cache)。

与CSS资源相似,JavaScript资源也是关键资源,JavaScript资源会阻塞DOM的构建。并且JavaScript会被CSS文件所阻塞。为了避免阻塞,可以为script标签添加async属性。

我们举个例子:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    <p class='_159h'>aa</p>
    <script src="http://qiniu.bkt.demos.so/static/js/app.53df42d5b7a0dbf52386.js"></script>
</body>
</html>
复制代码

上面这段代码,分别加载了CSS资源和JavaScript资源,我们使用Chrome开发者工具的Performance面板捕获出的结果如下图所示:

同步加载JS资源

从捕获出的结果可以看到,JS资源加载完毕后,需要等待CSS资源加载完并构建出CSSOM之后才会执行JS,并且JS会将DOM阻塞,所以最终domcontentloaded事件在350ms与400ms之间触发。

我们将script标签添加async属性:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    <p class='_159h'>aa</p>
    <script async src="http://qiniu.bkt.demos.so/static/js/app.53df42d5b7a0dbf52386.js"></script>
</body>
</html>
复制代码

使用Chrome开发者工具捕获出的结果如下图所示:

异步加载JS

从图中可以看到,JS加载完后不再需要等待CSS资源,并且也不再阻塞DOM的构建,最终domcontentloaded事件在50ms与100ms之间触发。与之前相比,domcontentloaded事件触发时间提前了300ms。

可以看到,在关键渲染路径中优化JavaScript,目的是为了减少关键资源的数量

总结

该篇文章详细介绍了如何优化关键渲染路径。

关键渲染路径是浏览器将HTML,CSS,JavaScript转换为屏幕上所呈现的实际像素的具体步骤。而优化关键渲染路径可以提高网页的呈现速度,也就是首屏渲染优化。

你会发现,我们介绍的内容都是如何优化DOM,CSSOM以及JavaScript,因为通常在关键渲染路径中,这些步骤的性能最差。这些步骤是导致首屏渲染速度慢的主要原因。

关注下面的标签,发现更多相似文章
评论