Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化关键渲染路径 #32

Open
berwin opened this issue Jan 21, 2019 · 3 comments
Open

优化关键渲染路径 #32

berwin opened this issue Jan 21, 2019 · 3 comments
Assignees
Labels
performance Web performance

Comments

@berwin
Copy link
Owner

berwin commented Jan 21, 2019

优化关键渲染路径

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

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

如何优化关键渲染路径?

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

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

这三种因素分别是:

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

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

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

关键路径的长度指的是浏览器和资源服务器之间的往返次数Round-Trip Time,通常被称作RTT。

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

关键路径中的每一步耗时越长,由于阻塞会导致渲染路径的整体耗时变长。

优化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,因为通常在关键渲染路径中,这些步骤的性能最差。这些步骤是导致首屏渲染速度慢的主要原因。

@berwin berwin added the performance Web performance label Jan 21, 2019
@berwin berwin self-assigned this Jan 21, 2019
@liang520
Copy link

优秀 啪啪啪👏👏👏

@berwin
Copy link
Owner Author

berwin commented Jan 21, 2019

@liang520 你是魔鬼么

@strongcode9527
Copy link

实在太棒了,干货满满

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Web performance
Projects
None yet
Development

No branches or pull requests

3 participants