网页的加载性能是影响用户体验的最重要因素,页面加载时间过长,极有可能会令用户直接关闭网页,即使网页本身的流程和UI等方面优化得再出色,也不会有任何价值。本文将以优化网页加载性能的角度出发,介绍网页渲染的过程以及各类资源阻塞网页渲染的情况,并给出优化的方向。
(本文以Chrome为主浏览器进行讨论,其他浏览器可能会存在细微不同的情况,不在本文讨论的范围)
网页的渲染过程
五个步骤
想要知道如何优化网页加载性能,首先得清楚浏览器加载一个网页经过了哪些步骤,才能明白应该从哪些方面着手优化。一般来说,浏览器从服务器端获取到HTML文件后就开始了加载过程,加载网页的过程包括如下五个步骤:
- HTML代码转化成DOM,并并行地获取外部资源(包括HTML中出现的JS、CSS、图片文件)
- CSS文件下载完成,CSS代码转化成CSSOM(CSS Object Model)
- 结合DOM和CSSOM,生成一棵渲染树(包含每个节点的视觉信息)
- 生成布局(Layout):将所有渲染树的所有节点进行平面合成,该步骤可以计算出元素在网页中的位置
- 绘制(Paint):将网页内容绘制在屏幕上
在加载过程中,前三步的耗时主要在于外部资源获取上,只有CSS资源下载完成才能完成第二第三步,生成渲染树;而后两步Layout和Paint较为耗时,且在网页渲染过程中会执行多次,其中首次Paint的耗时可以视为页面从空白到有内容的时间,减小这部分用时可以给用户以网页加载快的错觉。
浏览器实践
浏览器具备一些工具可以记录页面渲染的过程,进而帮助我们分析哪些方面的性能还有提升的空间。
以最简单的HTML结构来熟悉一下Chrome中的Performance工具(之前的名字是timeline)。
首先我们打开控制台,切换到Performance工具,在页面加载前点击左上角的圆点,开始录制,页面加载完再次点击,停止录制,等数据分析完成切换到“Event Log”就可以开始查看渲染过程了。
我们先以最简单的结构进行分析,如加载下方index.html文件:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试浏览器渲染</title>
<link rel="stylesheet" href="./assets/style.css">
</head>
<body>
<h1>koa by html file</h1>
<script src="./assets/script.js"></script>
<img width="300" src="./assets/image.png">
</body>
</html>
分析这个网页加载情况,如下截图所示:
(上图为渲染五个步骤中的第一步,接收html文件解析并并行请求外部资源)
(上图为2, 3和4, 5步,接收到CSS文件后构建渲染树,进行Layout和首次的Paint)
(上图为Load事件之后的Paint)
接着我们修改一下node层代码,令CSS文件返回延迟5s,会得到如下“Event Log”
(css文件延迟5s后,会发现首次Layout和Paint都延迟到5s之后)
为了研究JS文件对首次Paint的影响,我们令JS文件返回延迟5s,CSS和图片都即时返回,会得到如下“Event Log”
从上面的分析可以看出:
- 外部资源是在开头并行获取的,并非HTML解析到引用行再发请求的;
- CSS文件加载的速度是影响首次Paint时间的因素,当CSS文件下载完成后才能进行CSSOM和渲染树的构建,然后才是Layout和Paint过程;
- JS文件加载的速度不影响首次Paint时间,即使JS文件本身有修改DOM节点的操作,也只是在JS文件加载完成后再进行一次Layout和Paint;
各类资源阻塞网页加载的情况
阻塞网页加载的情况,可以分为两种:一种是阻塞HTML解析,即阻塞Event Log中的parse HTML步骤,令HTML结构停止解析;另一种是阻塞渲染,即阻塞Event Log中的Paint步骤,令已经解析好的HTML结构也无法显示到浏览器上。
CSS资源
CSS样式无法影响DOM结构,但能改变页面中元素呈现的模样,所以CSS资源可以阻塞渲染,但不会阻塞HTML解析。
为了研究CSS是否会阻塞渲染和解析,我们依然使用之前的index.html文件,只将style.css延迟5s返回,得到如下实验结果:
(解析完HTML之后,就并行发JS、CSS和图片请求,JS和图片先接收完成,但没有触发首次Paint渲染,而是延迟到css文件接收完成,CSSOM和渲染树构建完成才触发了首次Paint,因此CSS资源会阻塞渲染)
相同情况下,页面的展示和HTML结构如何呢?请看下方动图:
(在CSS文件接收完成之前,虽然HTML结构已经解析完成,即Elements中HTML结构已经被解析出来,但页面是一片空白,说明CSS资源不会阻塞解析,但会阻塞渲染)
JS资源
JS脚本会影响DOM结构,也能改变页面中元素呈现的模样,所以JS资源既能阻塞渲染又能阻塞HTML解析。
为了研究JS是否会阻塞渲染和解析,我们依然使用index.html文件,只将script.js延迟5s返回,得到如下实验结果:
(并行发送请求,CSS和图片资源先加载完,触发了首次Paint渲染,此时JS文件还没开始下载,所以JS文件并不会影响页面的首次渲染)
(延迟了5s后,开始接收JS文件,完成后,再次Paint渲染)
相同情况下,页面的展示和HTML结构,请看下方动图:
(JS开始下载之前,CSS资源已经下载完成,页面也解析并渲染出一部分内容,这部分是JS文件由script标签引入之前的内容,而script标签之后的图片则需等到JS文件加载完成后才进行解析并渲染出来,因此JS资源会阻塞之后的HTML结构解析和渲染)
图片资源
图片资源无法对其他DOM节点进行操作,所以不会阻塞渲染和HTML解析。
为了研究图片资源是否会阻塞渲染和解析,我们依然使用index.html文件,只将png文件延迟5s返回,得到如下实验结果:
(并行发送请求,CSS和JS文件都加载后,完成HTML解析和首次Paint渲染)
(当图片文件加载完成后,再次渲染)
相同情况下,页面的展示和HTML结构,请看下方动图:
(图片加载之前,页面的HTML结构已经解析完成,并且画面也出现在屏幕上,说明图片资源不会阻塞HTML的解析和画面渲染)
针对首屏的优化建议
从上面的实验可以看出,三种资源中只有CSS文件会影响首次Paint的时间。无论是延迟JS还是图片,在CSS文件加载完成后,都能先渲染出已经解析好的HTML内容。由于在首次Paint之前,页面是一片空白,所以首次Paint的时间也就相当于首屏时间。
针对首次Paint时间的优化,我们可以从以下角度进行考虑:
- CSS会影响首次Paint的时间,应该尽快加载,因此将其放在head标签处以保障能在解析完HTML后发出请求;
- JS不会影响首屏的时间,但由于JS会阻塞之后的页面内容的解析与渲染,要避免JS在首屏位置出现,一般将它们放在body的最后,另外,JS会影响整体页面加载的性能,可以使用defer和async转成非阻塞模式(见参考文献[6]);
- 图片不影响首屏时间,但会影响Load事件的时机(没有找到合适的参考文献,待我另起一篇文章补充);
参考文献
- 《js一定要放在底部吗?》https://segmentfault.com/a/11...
- 《聊聊浏览器的渲染机制》https://www.jianshu.com/p/c90...
- 《网页性能管理详解》http://www.ruanyifeng.com/blo...
- 《浏览器渲染过程与性能优化》https://sylvanassun.github.io...
- 《How browsers work》http://taligarsiel.com/Projec...
- 《JavaScript阻塞剖析与改善》http://www.cnblogs.com/giggle...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。