前言
最近在研究网站测速相关的主题,接触到一个概念:First Meaningful Paint,简称 FMP,中文译名:首次有效绘制时间。今天我们来讲讲这个概念的来龙去脉。
衡量页面打开速度是一件复杂的事情
First Meaningful Paint(以下简称 FMP),是谷歌创造的一个概念,用来更好地衡量页面打开的速度。你可能会说,要衡量页面打开速度,不是已经有 DOMContentLoaded Event 和 OnLoad Event 了吗?为什么还要搞一个新概念?
答案是:因为随着 Web 应用越来越复杂,DOMContentLoaded Event 和 OnLoad Event 已经越来越难以准确表现页面的打开速度了。
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这里是直出的内容</h1>
<img src="https://avatars3.githubusercontent.com/u/8401872" alt="">
<hr/>
<h1>下面是异步加载的内容</h1>
<div id="app"></div>
<script>
setTimeout(() => {
for (let i = 0; i < 100; i++) {
let newDiv = document.createElement("div");
let newContent = document.createTextNode("异步回来的内容" + i);
newDiv.appendChild(newContent);
document.querySelector('#app').appendChild(newDiv);
}
}, 200);
</script>
</body>
</html>
|
在这个例子中,html 中有直出的内容,也有一张图片,script 中用 setTimeout 模拟一个 Ajax 请求,数据返回后插入到页面中。像这种类型的页面非常常见,我们通过 Chrome 的 Performance 可以看到它的打开效果和速度。
观察 Timings,可以看到在打开页面的过程中依次触发了 DOMContentLoaded -> First Paint ->First Contentful Paint -> OnLoad -> First Meaningful Paint。我们尝试来解释一下。
- 浏览器首先加载解析 HTML,HTML 加载解析完成后,触发 DOMContentLoaded Event。此时浏览器尚未开始渲染,其他静态资源,比如图片等也还没加载。
- 浏览器开始渲染直出的 HTML,触发 First Paint 和 First Contentful Paint,用户开始看到文字。此时图片尚未加载回来,所以还不能看到图片。
PS:关于 First Paint 和 First Contentful Paint,网上的资料很少,我也没完全搞明白这两者的定义和区别。我大致是这样理解的:浏览器要开始渲染的时候,会先触发 First Paint,当渲染出第一个 DOM(比如文字或者图片等 DOM 元素)的时候,就会触发 First Contentful Paint。这两个概念并非本文的重点,更多的详细解析,请参考 W3C 的定义。 - 浏览器把图片加载回来了,所有资源都加载完成,触发 OnLoad Event。
- setTimeout 定时器触发,往页面中写入大量 DOM,触发 First Meaningful Paint。
那么问题来了,这里有 5 个指标,应该用哪个指标来衡量页面打开速度呢?
DOMContentLoaded 肯定不行,因为页面都还没出来;First Paint 和 First Contentful Paint 也不够好,因为这时候只有文字,图片和异步的数据都没出来。虽然从技术上角度看,页面是打开了。但是对于用户来说,用户想要关注的是下面的异步数据,异步数据还没出来,用户自然认为页面还没打开。OnLoad 也不够好,因为虽然图片出来了,但是异步数据还是没出来。
那哪个指标才能衡量异步数据出来的时间的呢? -> 从 Performance 中可以看出,这个指标就是 First Meaningful Paint,它最接近用户感知上的打开时间,所以它的名字叫 Meaningful,意思就是,对于用户来说,是有意义的,有效的。
综上所述,页面打开是一个非常复杂的过程,在这个过程中,有很多指标可以来表征页面打开不同阶段的情况。其中,最接近用户实际感知的,就是 First Meaningful Paint。
那么,下一个问题来了,这个 First Meaning Paint 是怎么定义的?Chrome 又是如何找到这个时间点的呢?
猜想,验证,优化
如果这个 Demo 就是我们的实际业务,想要衡量这个业务的打开速度,由于我们知晓其中的逻辑,知道只要等到那个 Ajax 回来之后,对于用户而言,就是 First Meaningful Time 了。所以我们可以通过自己打点来算出这个时间点。
问题:如果想要做一个通用化的工具,用来测量不同网站的 First Meaningful Time 呢?就跟 Chrome 的 Performance 一样,我们不可能知晓别的网站的业务逻辑,自然无法准确定义,到底哪个时刻才是 First Meaningful Time。怎么办呢?
答案:虽然无法准确知晓,但是可以靠猜测,关键是:怎么样猜测?猜得准不准?多准算准呢?
猜想一:布局重绘变化最大
在我们这个 Demo 中,异步数据回来后,会往页面中插入大量 DOM,势必会触发布局的重绘。浏览器可以检测出,什么时候发生最大的布局重绘变化,那么这个时刻就可以认为用户看到了关键内容,也就是 First Meaningful Paint 了。最大布局重绘变化,说起来比较抽象,可以通过另一个更加直观的指标来近似表征,就是页面中 DOM 节点的数量。下图便是 Demo 的 DOM 元素数量变化图。可以观察到,对应的时间是近似等于 First Meaningful Paint。
猜想二:考虑很长的页面
我们把 Demo 变一下,代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这里是直出的内容</h1>
<img src="https://avatars3.githubusercontent.com/u/8401872" alt="">
<hr/>
<h1>下面是异步加载的内容</h1>
<h2 id="title"></h2>
<div id="app"></div>
<script>
setTimeout(() => {
for (let i = 0; i < 200; i++) {
let newDiv = document.createElement("div");
let newContent = document.createTextNode("异步回来的内容 " + i);
newDiv.appendChild(newContent);
document.querySelector('#app').appendChild(newDiv);
}
document.querySelector('#title').innerText = "第 1 次异步";
}, 200);
setTimeout(() => {
for (let i = 0; i < 1000; i++) {
let newDiv = document.createElement("div");
let newContent = document.createTextNode("异步回来的更多内容 " + i);
newDiv.appendChild(newContent);
document.querySelector('#app').appendChild(newDiv);
}
document.querySelector('#title').innerText = "第 2 次异步,更多的内容";
}, 500);
</script>
</body>
</html>
|
这样的场景就相当于:我先 Ajax 加载第一页的内容,等用户先看到内容了,然后我再发起下一页的 Ajax 请求,拿回来更多的数据。此时,假如第 2 次请求触发了更大的布局重绘,那按照猜想 1 的逻辑,那 First Meaningful Paint 岂不是要推迟到第 2 次请求回来?
但是对于用户来说,第 1 次请求回来就感知到页面打开了,后面的第 2 次请求,不过是让页面变得更长而已,用户实际上不会马上看到第 2 次请求的数据。所以,还需要在猜想一的基础上再优化。谷歌的做法就是:引入权重的概念,不仅考虑重绘的大小,还要考虑当重绘发生时,页面高度上发生的变化。比如第 2 次请求回来,虽然发生了很大的重绘,但是页面高度也发生了很大的变化,因此,总体加权之后的重绘大小就变小了。我们在 Performance 中测试,可以看到,Chrome 依然准确地判断到了 First Meaningful Paint 是第 1 次请求回来,而不是第 2 次。如下图所示。
当然,还有更多的其他场景需要考虑(比如字体加载),具体的可以参考谷歌的研究资料。
后话
First Meaningful Paint 的确是一个很有用的指标,让我们可以更多地从用户的角度去衡量页面打开速度。但是,想要做成一个通用化的工具,这个指标就只能靠猜测,即便是谷歌,也不能百分百保证猜得准。比如说,把 Demo 改成,第 1 次插入 100 条数据,第 2 次插入 500 条数据,经测试,Chrome 就会猜测错误,将 First Meaningful Paint 误判为第 2 次请求。不过值得关注的是,谷歌在严谨的实验中,也给出了猜测算法的测试样本和准确率,感兴趣的读者,强烈建议读一下谷歌的研究资料。
参考资料
原创文章转载请注明:
转载自AlloyTeam:http://www.alloyteam.com/2019/12/14174/