You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
这是一个最常见的单页应用形态。bundle.js 下载完后,执行,构建 DOM 树,替换 div#app 节点,渲染应用。那么问题来了,这段用来测试首屏渲染的文字,会不会被渲染到屏幕上?查询已有的资料,主要从两个方面讲解:
浏览器解析页面流程:
解析 HTML,构建 DOM 树
解析 CSS,构建 CSSOM
合并 DOM 和 CSSOM,构建渲染树(render tree)
对渲染树进行布局,得到每个节点的位置、尺寸信息
对渲染树进行绘制。
由于脚本是阻塞 html 解析的,只有下载、执行完,html 解析才宣告结束,此时构建的渲染树是完全的,但也已经不再有测试文字节点了。而在脚本下载、执行完之前,这个『不完整的渲染树』会渲染吗?得不出确切的结论。
『需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。』 - 来自『浏览器的工作原理:新式网络浏览器幕后揭秘』
在网上检索『首次渲染』、『when does browser first paint』找不到相关的资料。在搜索时,突然发现一个新的 API PerformancePaintTiming,可以通过 first-paint 和 first-contentful-paint 这两个 entry name 来获取首次渲染的时间。赶快去查阅它的规范:
4.1.1. Mark paint timing
Perform the following steps:
Let paint-timestamp be the input timestamp.
If this instance of update the rendering is the first paint, then record the timestamp as paint-timestamp and invoke the §4.1.2 Report paint timing algorithm with two arguments: "first-paint" and paint-timestamp.
NOTE: First paint excludes the default background paint, but includes non-default background paint.(这里可以发现,默认的白屏不算 first-paint,至少得设个背景色)
Otherwise, if this instance of update the rendering is the first contentful paint, then record the timestamp as paint-timestamp and invoke the §4.1.2 Report paint timing algorithm with two arguments: "first-contentful-paint" and paint-timestamp.
NOTE: This paint must include text, image (including background images), non-white canvas or SVG.(写了字,放了图片,就算 first-contentful-paint 啦)
翻译:如果 update the rendering 实例是 first-paint 那么就记录时间戳,上报为 first-paint 时间。如果 update the rendering 实例是 first-contentful-paint 那么就记录时间戳,上报为 first-contentful-paint 时间。
title: 对浏览器首次渲染时间点的探究
categories:
tags:
toc: true
date: 2019-04-23 22:17:10
使用 Chrome Devtool 进行性能分析时,在 Performance 面板上,可以看到用绿线标出来的
First-Contentful-Paint
。浏览器何时进行首次渲染?网上只能查到一些模棱两可的资料,今天我们来探究这个问题。1. 引子
1.1 术语堪明
在掘金上用『首次渲染』进行搜索,查不到什么相关资料;使用『首屏时间』进行搜索,能搜出大量性能优化的文章。点进去看可以发现,大家常谈的『首屏时间』是一个业务概念,指的是业务的首屏内容全部渲染完毕的时间点,一般使用埋点进行手动上报。本文探索的则是浏览器进行首次渲染的时间点,此时可能只渲染出了网页的部分内容。
浏览器何时进行首次渲染?
1.2 提出场景
举例说明:
这是一个最常见的单页应用形态。
bundle.js
下载完后,执行,构建 DOM 树,替换div#app
节点,渲染应用。那么问题来了,这段用来测试首屏渲染的文字,会不会被渲染到屏幕上?查询已有的资料,主要从两个方面讲解:由于脚本是阻塞 html 解析的,只有下载、执行完,html 解析才宣告结束,此时构建的渲染树是完全的,但也已经不再有测试文字节点了。而在脚本下载、执行完之前,这个『不完整的渲染树』会渲染吗?得不出确切的结论。
这篇讲解浏览器工作内幕的经典文章表示:HTML 解析完毕之前,也是可以进行绘制的,那么测试文字一定就能绘制出来么?依然没有明确的答案,感觉像是浏览器的黑箱。没有办法啦,只能自己去尽量检索了。
2. 规范解读
2.1 stage1: paint timing 规范
在网上检索『首次渲染』、『when does browser first paint』找不到相关的资料。在搜索时,突然发现一个新的 API PerformancePaintTiming,可以通过
first-paint
和first-contentful-paint
这两个entry name
来获取首次渲染的时间。赶快去查阅它的规范:翻译:如果
update the rendering
实例是first-paint
那么就记录时间戳,上报为first-paint
时间。如果update the rendering
实例是first-contentful-paint
那么就记录时间戳,上报为first-contentful-paint
时间。[
update the rendering
]((https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering)是啥?点进去,规范直接跳到了eventloop
。恍然大悟,update the rendering
不就是eventloop
中的最后一个阶段吗!不熟悉 eventloop 的同学可查阅之前文章:[深入探究 eventloop 与浏览器渲染的时序问题](https://www.404forest.com/2017/07/18/how-javascript-actually-works-eventloop-and-uirendering/)
原来浏览器对于首次渲染根本就没有什么『黑箱操作』,人家只是老老实实的按照 `eventloop` 来运行而已。`eventloop` 第一次进行到 `update the rendering` 阶段的时间点那就是 `first-paint` 的时间点了。于是我们下一步来研究,HTML 解析过程中,`eventloop` 是怎么运行的?
2.2 stage2: eventloop 规范
我们知道
eventloop
按照task > microtask > render
的顺序执行。查阅规范中关于task
的定义,得:HTML 解析是一个典型的
task
。task
执行完才能render
,正如 HTML 解析完才能渲染,很合理。然而经典文章说了,明明可以边解析边绘制的,事情肯定不会这么简单。2.3 stage3: html parser 规范
在
html parser
规范中检索eventloop
得:我们已经知道 CSS 的加载是会阻碍 JS 执行的。而脚本不处于这个
ready to be parser-executed
状态简单理解就是还没下载完。如果出现这两种情况,脚本就无法立刻执行,需要等待。此时要进行 spin the eventloop,查阅规范,该操作即为:简单的说就是让
eventloop
中断并暂存当前正在执行的 task/microtask,保持eventloop
的继续执行,待一段时间之后满足条件了再恢复之前的 task/microtask。那么问题就水落石出了:
如果在 HTML 解析过程中,『解析到了某个脚本,但这个脚本被 CSS 阻塞住了或者还没下载完』,则会中断暂存当前的解析
task
,继续执行eventloop
,网页被渲染。如果 JS 全部是内联的,或者网速好,在解析到
</script>
时脚本全都已下载完了,则解析 task 不会被中断,也就不会出现渲染情况了。3. 实战测试
对于 1.2 中的例子,我们禁用缓存,使用 chrome 模拟 3G 网速,测试结果:
在 bundle.js 加载之前,测试文字被渲染出来了
`First-Contentful-Paint`在很早的位置
可验证之前的结论:HTML 解析过程中遇到脚本且脚本处于等待执行状态(被CSS阻塞/没下载完),解析中断,进行渲染。我们开启缓存,不限速,让 bundle.js 走强缓存,瞬间加载:
`First-Contentful-Paint`在 HTML 解析之后
此时解析 Task 不被中断,渲染只能等到 HTML 解析完成之后再执行啦。
4. 题外话
笔者弄清该问题,花了一两个小时,写这篇文章又花了仨小时,查了不少资料,还是小有收获的,比如骨架屏的原理就是在解析中断时提早渲染页面,顺带巩固了 eventloop 和浏览器渲染机制。在 sf 上看到了有人跟我有同样的问题:
哇,遇到同样的探索者真难得!本是开心的准备迎接知识的海洋,然后:
5. 参考资料
The text was updated successfully, but these errors were encountered: