CSS 资源的加载情况对浏览器渲染页面的影响很大,这是因为默认情况下浏览器只有在完成 <head>
部分 CSS 样式的加载、解析之后才会渲染页面。这种渲染方式意味着,如果 CSS 文件很大,那么用户就必须等待很长的时间才能看到渲染结果。针对这一问题,我们将在接下来的内容中讨论一种非常规的解决方案,提高页面的渲染速度,这一方案常被称为 critical rendering path。
我们要做的优化就是找出渲染首屏的最小 CSS 集合(Critical CSS),并把它们写到 <head>
部分,从而让浏览器接收到 HTML 文件后就尽快渲染出页面。对于剩余部分的 CSS,我们可以使用异步的方式进行加载。对于如何判断哪些 CSS 是首屏渲染需要的,我们需要通过分析 DOM 结构来确定,当然,手动来处理这些工作就太单调乏味了,所以在后文我会为大家介绍几种自动化处理的工具。
实战
首先,我们再重新梳理一遍步骤:将样式分成两份,一份是渲染首屏的最小集合,另一份是渲染页面剩余部分的样式,对于第一份,将其内嵌在 <head>
部分,对于第二部分则使用异步的方式加载。这种解决方案初看起来有些怪异,但实质上很简单,通过一次请求就获取到 HTML 文档和渲染首屏的 CSS 样式,尽快地为用户提供响应结果。为了更好地理解这一方案,下面的代码演示了核心的 HTML 结构:
<!doctype html>
<html>
<head>
<style> /* inlined critical CSS */ </style>
<script> loadCSS('non-critical.css'); </script>
</head>
<body>
...body goes here
</body>
</html>
在上面的代码中,我们取出首屏渲染所必须的 critical CSS,以内联的方式写在 <style></style>
之中,然后使用 loadCSS()
异步加载剩余部分的 CSS 样式,这相当于离线加载剩余部分的 CSS 样式,然后在后台将其注入到页面中。
看完结构我们说说问题,首先就是这看起来很难以维护,对于这个问题,我想说何必手工处理这两部分的样式呢,让自动化工具来帮你自动处理吧,比如说下文中演示的 Critical,它是一个 Node.js 的第三方库,帮助开发者自动筛选 critical CSS。
接下来我们就使用 Grunt 搭配 Critical 自动化处理 CSS,如果你还没有安装过 Grunt,可以使用以下命令在全局安装 Grunt:
npm install -g grunt-cli
接下来,安装 Grunt 任务执行器:
npm install grunt --save-dev
安装 grunt-critical插件:
npm install grunt-critical --save-dev
然后,为 Grunt 创建配置文件,如下所示:
module.exports = function (grunt) {
grunt.initConfig({
critical: {
dist: {
options: {
base: './'
},
// The source file
src: 'page.html',
// The destination file
dest: 'result.html'
}
}
});
// Load the plugins
grunt.loadNpmTasks('grunt-critical');
// Default tasks.
grunt.registerTask('default', ['critical']);
};
在上面的配置文件中,我们对 Critical 进行了配置,让其检查 page.html
文件的 CSS 样式,并计算出该页面的 Critical CSS,分析结束后它会将 Critical CSS 内联到该页面。
通过在终端输入 grunt
运行该插件:
执行完成上述命令之后进入相关的文件夹,你会发现文件夹中多出了一个 result.html
文件,该文件包含了一个内联的 CSS,并使用异步的方式加载剩余的 CSS。
实际上该插件使用无界面的 Webkit 浏览器 PhantomJS截取 Critical CSS。这也就是说,它会自动地加载指定页面并测试最佳的 Critical CSS。如果你想对该功能做更细致的配置,比如说配置不同的屏幕尺寸,那么可以仿照下面的配置文件进行修改:
critical: {
dist: {
options: {
base: './',
dimensions: [{
width: 1300,
height: 900
},{
width: 500,
height: 900
}]
},
files: [
{src: ['index.html'], dest: 'dist/index.html'}
]
}
}
通过上面的配置,Critical 插件将会为页面生成多尺寸的内联 Critical CSS,让使用不同设备的用户拥有相同的体验效果。如我们所知,3G 或 4G 等移动信号是不稳定的,这也是我们使用这一解决方案的初衷以及其重要性的体现。
线上环境
使用 Critical 等工具是一个自动化实现 Critical CSS 的好方案,但是它是否适应真实的开发环境呢?毫无问题,就像你之前的部署一样方便,唯一需要牢记的就是每次构建或修改 CSS 之后都要执行 Grunt 来处理 Critical CSS。
上面的 Grunt 配置已经可以很好地处理单个文件了,如果要处理多个文件或者整个文件夹,那么需要将 Grunt 的配置文件更新为如下形式:
critical: {
dist: {
options: {
base: './',
dimensions: [{
width: 1300,
height: 900
},{
width: 500,
height: 900
}]
},
files: [
{src: ['index.html'], dest: 'dist/index.html'},
{src: ['blog.html'], dest: 'dist/blog.html'}
{src: ['about.html'], dest: 'dist/about.html'}
{src: ['contact.html'], dest: 'dist/contact.html'}
]
}
}
或者使用下面的配置方法处理整个目录下的文件:
critical: {
dist: {
options: {
base: './',
dimensions: [{
width: 1300,
height: 900
},{
width: 500,
height: 900
}],
src: '*.html',
dest: 'dist/'
}
}
}
测试
如果想测试使用 Critical 前后的性能改变,推荐你使用免费的在线工具 Google Pagespeed Insights,只需输入指定页面的链接即可。该工具会自动加载页面,你会发现页面的渲染阻塞时间变少了,非常棒。另一个非常好用的工具是 WebPagetest。
WebPagetest 是一个免费的工具,用于帮助开发者在全球各地测试网站速度,测试完成会提供一个详细的测试结果信息。如果选择使用 Visual Comparison,则可以添加多个链接地址进行比较,便于比较使用 Critical CSS 前后的性能表现。
使用 Critical CSS 的根本原因就是尽快为用户呈现页面,使用 speed index就可以对页面内容的呈现时间进行测试,它也是由 WebPagetest 提供的功能。SpeedIndex 会测量页面渲染的整个流程,最终计算出一个分数值量化页面渲染的速度。如果你已经完成了上面使用 Grunt 拆分 Critical CSS 的操作,那么现在就可以使用 SpeedIndex 进行测试了,结果可能会吓你一跳哈。
深入浅出
对于大多数的性能优化技巧都存在其优点和缺陷,Critical CSS 的弊端在于内联样式是无法被浏览器缓存的。对于动态变化的页面,HTML 通常是不做缓存处理的,这也就是说内联在 HTML 中的 Critical CSS 会被反复下载。对于使用内联样式的争论已经很多了,我建议你阅读 Hans Christian Reinl 的文章 《A counter statement: Putting the CSS in the head》。如果你在使用 CDN,那么建议你使用 CDN 缓存页面的 non-Critical CSS,减少资源的响应时间,避免直接向原始服务器获取资源。
使用内联 CSS 的方案也可以很好地兼容传统的网页,但是否适用还要取决于你的使用场景,比如是否是用 JavaScript 生成 HTML 的页面,是否是单页应用。理解 Critical CSS 的工作机制和适用条件非常重要,我非常喜欢 Guy Podjarny 的这句话:
虽然有种种限制,但是内联样式仍是前端性能优化的重要方式,因此,你可以用它,但也需要慎重,以免误用。
在 《Why Inlining Everything Is NOT The Answer》一文中,他就使用内联样式的时机做了更详细的分析。
瑕疵
因为这里面使用到了多个工具,而且对 Critical CSS 的计算也在改善之中,所以如果你遇到了 Bug,请在 GitHub 项目主页上提交 issue或 pull request。优化网站的 Critical Rendering Path 是一个长远的过程,使用本文所介绍的优化方案可以让页面是一个响应式的,也不会对页面设计提出额外的要求。
更多资料
如果你喜欢使用 Gulp,那么可以参考文章 《how to optimize a basic page with Gulp》。此外,用于筛选 Critical CSS 的插件还有 Penthouse和 criticalCSS。在这里也建议你阅读 Filamen Group 的文章 《How we make RWD sites load fast as heck》,看一看他们是怎么使用本文的方案提高页面加载速度的。
Smashing Magazine 的主编 Vitaly Friedman 写过一篇有关 Smashing Magazine 使用该方案优化页面性能的文档,如果你喜欢获取更多有关 Critical Rendering Path 的文章,可以观看 Udacity 网站的免费课程。Google 开发者网站也有很多优化 CSS 加载的文章,非常值得一看。Patrick Hamman 也写了很多优秀的文章 how to identify critical CSS,演示如何优化网站性能。
本文根据@Dean Hume的《Understanding Critical CSS》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://www.smashingmagazine.com/2015/08/understanding-critical-css/。
如需转载,烦请注明出处:http://www.w3cplus.com/css/understanding-critical-css.html