First paint 直译过来的意思就是浏览器第一次渲染(paint),在First paint之前是白屏,在这个时间点之后用户就能看到(部分)页面内容。
所以研究这个First Paint的触发时机对于优化浏览器页面的首屏渲染时间有很重要的作用。
在正题开始之前,先说下浏览器的页面的加载流程(大体过程是这样,并不精确,只是为了帮助理解后面内容):
在最新版的Chrome的perfomance中是能直接看到First Paint这个时间点的,为了方便大家测试,我就直接拿谷歌这个示例页面来做演示:
测试页面
用chrome打开上面链接,最好是隐身模式,防止插件乱入影响判断,按F12或者右键检查元素打开控制台先切换到Network选项,勾选禁用缓存(缓存也会影响到判断):
切换到Perfomance,勾选Screenshots并点击红框进行页面分析(会自动停止的,不用点stop):
分析完后可以看到如下结果:
上图中的绿色的线就是当前页面第一次出现内容的时间点,可以将鼠标放到Main上面的Network中绿色的线附近可以看到在他之前页面空白,在他之后就有内容。 除了绿色的线还有蓝色以及红色的线,这里也解释一下:
简单讲一下DOMContentLoaded、load的区别:
这个时候发现绿色虚线之前有一个浅绿色方块,相应的解释如下:
由图可以得出“浅绿色”代表的是根据CSSOM计算样式并进行布局绘制的过程,这段时间内浏览器做了一下事情:
那什么时候开始First paint呢?在浅绿色方块最前面的虚线往前看,发现在灰色虚线之前都会有一个步骤:就是Parse Stylesheet(调研了很多页面都是如此)
所以,First Paint的加载流程应该是这样:
但是现在还只是确定了First Paint的加载流程,也确定了他是在所有CSS执行完Parse Stylesheet之后才会触发,但是这还是不够准确啊,所以我找了一些CSS和JS的外链来测试,模板如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.css" rel="stylesheet"> <link href="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/vue/2.5.13/vue.js"></script> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script src="https://cdn.bootcss.com/react/16.4.0-alpha.0911da3/cjs/react.development.js"></script> <script src="https://cdn.bootcss.com/angular.js/2.0.0-beta.17/angular2.js"></script> </head> <body> <div id='root1'> 1 </div> <div id='root2'> 2 </div> <div id='root3'> 3 </div> </body> </html>
我们通过改变上面模板里的外链顺序来探究:
第一种情况:
发现FP发生在最后(实心的蓝色线是按shift出来的,不是DOMContentLoaded),现在还发现不了什么。
第二种情况:
调换head中CSS和JS外链位置
仍然发现不了什么
第三种情况
把CSS放head,JS放</body>前
发现FP竟然在蓝色和红色虚线前面出现,通过这点可以确定,FP还跟JS外链的位置有关,继续:
第四种情况:
JS外链放head,CSS放</body>前
发现又跟第一二种情况一样了,所以这种用法是不可取的。
第五种情况:
CSS和JS都放</body>前,且CSS紧贴在div后面,JS在CSS后面:
可以发现FP居然更快触发,但是我鼠标hover到绿色虚线后,仍然是白屏,只有等到CSS加载完成执行Parse Stylesheet之后才显示出内容(说明这种用法也不可取),难道body中的CSS也会影响?
第六种情况:
掉换一下上面CSS和JS的位置:
发现这次FP触发而且立马有内容,而等到CSS加载完成之后还会再重新渲染一次,嗯,看来body中的第一个JS脚本有猫腻,接下来的情况对他特殊照顾。
第七种情况:
CSS放head中,JS放在div节点中间:
哈哈,居然只渲染了12俩字,说明浏览器会渲染body中脚本之前的内容,那会是哪个脚本之前的内容呢?
第八种情况:
在div之间都插入脚本
看来浏览器会提前渲染body中第一个脚本前的内容(我们就把body中的第一个外链脚本叫做【第一脚本】吧),并且第一脚本还会在FP之后才执行。所以结合之前得出的结论,在CSSOM准备就绪之后,浏览器会提前渲染第一脚本前的内容,我们可以用第九种情况来验证:
第九种情况:
这种情况和上种没什么区别,只是增加了一个CSS,这个CSS中还会发出一个请求去加载其他CSS(通过@import url()的方式),所以CSS的加载时间很长。
通过结果可以看出,123在CSS下载完成之后才渲染,而不是单独渲染一个1,所以FP必须得等到CSSOM准备就绪之后才会触发,否则即使有第一脚本在也没用。 所以到这里,我们总算可以下结论了:
FP发生在body中第一个script脚本之前的CSS解析和JS执行完成之后。换句话说就是第一脚本之前的DOM和CSSOM准备就绪之后,便会着手渲染第一脚本前的内容。
但是...你以为到这里就结束了?其实没有。
第十种情况:
这种情况中,head中既有JS也有CSS,body中也有第一脚本存在:
注意上图中的vue.js是在head中的,而后面的JS文件都在body中,而且,vue.js加载完成之后,body中的JS还没下载完成,这个时候我们调换一下vue.js和angular2.js的位置:
看,这个时候又没有提前渲染了,123等到所有JS文件都执行完之后才渲染,这种情况除了验证了第九点的结论,还能补充我们的结论:
如果第一脚本前的JS和CSS加载完了,body中的脚本还未下载完成,那么浏览器就会利用构建好的局部CSSOM和DOM提前渲染第一脚本前的内容(触发FP);如果第一脚本前的JS和CSS都还没下载完成,body中的脚本就已经下载完了,那么浏览器就会在所有JS脚本都执行完之后才触发FP。
到这里本次探究就结束了,其实还有很多种情况,感兴趣的可以自己去试试。
建议:
点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 {我},享受文章首发体验!
每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”
原文链接:http://eux.baidu.com/blog/fe/Chrome%E7%9A%84First%20Paint
作者:洪闰辉