网页下载,这是和生命在赛跑
|

网页提速,你的小抄来了

网页下载,这是和生命在赛跑
网页下载,这是和生命在赛跑

人人都知道网页秒开有多重要,它不但能提高转化率而且能增加SEO效果,不管是谷歌还是百度都把网页载入速度归为重要的排名因素,毕竟没有一个搜索引擎会把用户送去体验超级差的页面。SEO中的鄙视链有很多种,比如国际化SEO鄙视本地SEO,白帽SEO鄙视黑帽SEO(当然黑帽是不屑的,偷着乐),还有技术SEO鄙视非技术SEO。极诣本篇将为读者从技术角度解读页面载入速度中的关键渲染路径,关键渲染路径决定了首屏展现速度。如果你缺乏前端开发的知识,那么请记住结论。

TL;DR

  1. 减少关键渲染路径中的阻塞资源是提升页面性能加快网页下载的重要方法。
  2. CSS资源要放在页面头部要权衡整站和单独页面,通过内嵌、合并、瘦身、共用缓存来进行优化。
  3. 外部JS文件要放在页面末尾并应用async或defer属性减少阻塞,也可以进行“懒加载”实现。
  4. DOMInteractive是谷歌常用的指标用于衡量可交互时间,优化方法同上。

关键渲染路径的基本思路

除了保证优秀的Hosting(开启HTTP/2与压缩)和应用CDN和本地缓存,优化页面的关键渲染路径(Critical Rendering Path)也是提升网页速度和用户体验的重要方法。而优化页面的关键渲染路径包含了三件事:

  1. 减少关键资源请求数
  2. 减少关键资源大小
  3. 缩短关键渲染路径

第一点我们可以通过合并资源来实现,比如合并CSS文件,合并JS文件,合并一些小图片,也可以使用矢量或Base64格式图片,图片懒加载(Lazy Load)等实现。第二点我们可以通过GZIP压缩和图片资源压缩来实现。我们说关键的第三点。缩短关键渲染路径体现在减少页面阻塞资源上。

页面加载的阻塞资源

我们必须了解网页载入的整个过程,我们在前文讲《Google Analytics跟踪网站页面速度》中介绍过,见下图:

页面打开的整个过程
页面打开的整个过程

浏览器会下载HTML文档逐行来解析(Parse)。当读到CSS和JS时整个过程便会被阻塞。

CSS的阻塞影响

浏览器读到外部CSS时会异步地对外部CSS进行下载,然后逐步组合各个CSS样式成为CSSOM。这里CSS文件还可能导入其他的CSS文件。如果CSS没处理完HTML这边建的DOM就不会和CSSOM合并为Render Tree。

DOM和CSSOM结合为Render Tree
DOM和CSSOM结合为Render Tree

这就好比此时的DOM是未受精的鸡蛋,孵不出小鸡。这就会阻塞JS的运行,因为JS很可能需要CSS的标记。CSS是无法Lazy Load的阻塞资源。我们可以通过合并CSS文件减少请求数,删除不需要的样式片段,以及把CSS写到HTML文件内的inline方法来进行优化。由于我们需要有效的利用缓存,因此我们也需要在上面这些措施和缓存之间进行平衡。

JS的阻塞影响

有了JS的情况就复杂一些了。当浏览器解析到JS片段时(标签为<script>),解析器就会停下来,这个JS片段后所有的资源都会等着。如果是外部JS那么就先去下载,再去运行。运行完之前这个JS片段后所有的HTML都会被无视。

那你可能要问了,这片段上面的HTML建DOM Tree还没建完呢?那是不是就必须干等着JS跑完呢?不是这样的,这里有一个首次绘制(First Paint)的概念。

原来现代浏览器遇到这种情况会立刻拿已有的半截DOM Tree和已经完成的CSSOM进行首次绘制。这就是为什么老人们总是说要把JS放在</body>前面。因为这样这个半截DOM Tree就近似于完整的DOM Tree了。老人们还说要把CSS放在</head>前面,这是因为如果放在阻塞的JS之后就无法在首次绘制的时候被应用,解析器是无视JS片段之后的代码的。首次绘制之前,我们看到的浏览器是个白屏。

为了专门应付这个情况浏览器和HTML也在进化。现在我们用的大多数浏览器都支持async和defer两个属性。这两个属性的共同点是不会阻塞解析器继续解析JS片段下的HTML,区别是async在脚本下载后(如果CSSOM已完成)便会阻塞解析器开始执行,而defer要等Render Tree完成了才会执行。

async与defer的作用
async与defer的作用

因此我们优化JS的方式就是将对首屏内容起影响的JS内容作为inline放到HTML中,而将其他的JS文件设置为async或者defer,最后为了满足古董浏览器把它们放在</body>之前

这里我们要注意的是async和defer虽然能让外部JS变成非阻塞资源,让Google PageSpeed Insights不再圈出它们,但是并不能让JS资源真正在页面完成后才载入。如果你想要“Lazy Load”一下JS资源可以采用Patrick Sexton提供的方法(建议去读一下他的页面性能指南)。这样只有在这段代码执行以后才会加载defer.js,而这段代码一定要全部页面Loaded以后才会加载并运行defer.js。

<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "defer.js";
document.body.appendChild(element);
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>

说到这里,我们解释了阻塞资源和如何避免阻塞资源带来的影响。而且在上面代码中,我们提到了load这个这个浏览器event。我们在本篇最后再谈谈另外两个与谷歌判断网页速度相关的事件——DOMContentLoaded和DOMInteractive

DOMContentLoaded与DOMInteractive

DOMContentLoaded代表着DOM和CSSOM都已经就绪,所有阻塞的JS资源都已经执行完毕。“吉时已到”!如果本身就不存在阻塞的JS资源,那么在DOMInteractive事件之后就会立即发生DOMContentLoaded事件。

DOMContentLoaded在Chrome DevTools中
DOMContentLoaded在Chrome DevTools中

DOMContentLoaded可以用于我们的KPI,这个时刻越早代表了我们越早能够有Render Tree,我们遇到的阻塞越少

DOMInteractive事件也常常被用于衡量页面性能。它标志了DOM已经构建完毕,如果这个时候CSSOM也构建完毕且没有阻塞JS资源,那么将马上发生DOMContentLoaded事件。相对于DOMContentLoaded,DOMInteractive事件主要被阻塞的JS资源影响比较这两者的区别可以给我一些优化启示。

Google Analytics用DOMInteractive作为衡量网页速度的指标之一,但是下面Google Analytics文档的描述是错误的

Avg. Document Interactive Time : The average time (in seconds) that the browser takes to parse the document (DOMInteractive), including the network time from the user’s location to your server. At this time, the user can interact with the Document Object Model even though it is not fully loaded.

恰恰相反,我们说了DOMInteractive代表整个HTML已经解析完了,DOM已经构建。正是由于一些阻塞资源的影响,DOMInteractive事件之前用户可能已经因为首次绘制看到了部分内容(JS阻塞资源影响);而且即便DOMInteractive事件已经发生,用户也可能因为CSSOM未完成看到的还是白屏(CSS阻塞资源影响)。因此只能说DOMInteractive只是一个折衷的指标。至于Google的PageSpeed Insights中的可交互耗时(TTI-Time to Interactive)非常有意思的是它既没有用First Paint也没有用DOMInteractive来进行衡量,而是用了First Contentful Paint。原因是前两个指标不能衡量此时用户是否能真正开始交互,虽然愿望很美好:

谷歌对可交互时间TTI的定义
谷歌对可交互时间TTI的定义

要提高TTI的评分还是要注意消除阻塞资源的影响。

总结

本篇我们围绕着关键渲染路径介绍了优化页面性能的方法,这有助于你在GA中和PageSpeed Insights中取得好成绩。但是你的最终目标是为用户提供优秀的交互体验,请牢记这个目标。笔者只是一个热爱前端技术的SEO或者热爱前端技术的网页分析师,理解和行文难免有所疏漏,希望不啬指出。

类似文章