FMP(全称“First Meaningful Paint”,翻译为“首次有效绘制”)表示页面的“主要内容”开始出现在屏幕上的时间点。它是我们测量用户加载体验的主要指标。
通常我们使用测评工具(例如:Lighthouse)就可以得到FMP值。但是这里有一个问题是:不同产品的“主要内容”是不一样的;对于博客,主要内容是文章标题+首屏文本(可见的文本)、对于搜索引擎主要内容就是搜索结果。
只有我们自己最清楚我们产品的主要内容是什么,那么测评工具是如何捕获出FMP值的?它捕获出的这个FMP准么?
本文我们将针对这两个问题进行详细的讨论。
本小节我们将介绍一种基于布局的方法来捕获FMP,它的准确率可以达到77%。
随着网页的加载与解析,浏览器会将布局对象(Layout Object)逐步添加到布局树(Layout Tree)上进行布局。
以Google搜索结果页为例,下图给出了该页面在加载时布局对象被添加到布局树中的数量和时间(横坐标为时间,纵坐标为数量)。
图1 - 布局对象
FCP(First Contentful Paint)的时间在1.577秒,此时已经有60个布局对象被添加到了布局树中,这时候页面只渲染了一个Header;在1.86秒的时候,有261个布局对象被添加到布局树中,这些对象是搜索结果,随后在1.9秒进行了一个绘制(Paint),这个绘制是FMP;随后一些剩余的页面底部等部分的布局对象被添加到布局树中并进行绘制,最终页面在2.425秒完成。
从这个例子中我们会发现,布局对象的数量与页面加载的完整性密切相关。
经过大量试验与测试,最终发现大量的新布局对象被添加到布局树中的时间,和FMP非常接近。并且在FMP这个场景下,新布局对象的数量比重新布局的布局对象数量更重要。
所以我们得出一个关于FMP的公式:
FMP = 最大布局变动之后的那个绘制(Paint)
最大布局变动指的是哪个时间点布局对象被添加到布局树中的数量是最大的。
如图1所示,最大布局变动在1.86秒,而下一个绘制时间是1.907秒,所以1.907这个时间是FMP。
通过这种方式捕获出来的FMP,精准度大概在57.1%,比单纯的FCP强很多,但是在多数情况下,它还是没有办法捕获出真正有意义的绘制。
假设我们有一个很长的页面,下图给出了这个页面的布局对象被添加到布局树中的数量和时间:
图2 - 布局对象2
在图2中,该页面最主要的内容在6.047秒被绘制出来,所以这个页面的FMP应该是6.047秒,但是如果按照我们前面的算法,则给出的FMP是24.25秒,因为23.8秒是最大布局变动,而最大布局变动的下一个绘制时间是24.25秒。
但事实是24.25秒这个绘制并不重要,因为它在屏幕之外的区域进行绘制,用户根本看不到这部分内容。
如何防止这些屏幕外的布局扰乱测量FMP的精准度?最理想的方式是计算元素是否可见,但是在布局期间进行昂贵的计算也不是一个好办法。所以解决方案是使用“权重”,当布局对象的位置超过屏幕的高度时,降级它的权重。所以现在我们使用布局的“意义”来捕获FMP,而不是布局对象的“数量”。
所以我们可以得出一个改进后的计算FMP的公式:
意义 = 当前时间布局对象的数量 / max(1, 页面高度/屏幕高度)
FMP = 意义值最大的一次布局变动之后的那个绘制(Paint)
注意:在这个公式中,页面高度 = 当前布局对象在页面中的位置
下图给出了使用该公式计算出的“布局意义”流线图:
图5 - 布局意义
现在,意义最大的一次布局变动在5.89秒,下一次绘制的时间是6.047秒,而这个时间就是FMP。通过这个算法捕获出的FMP准确率可以达到62.1%。
Web字体可以打破这个算法捕获FMP的准确率,假设某个网页的最大布局变动是在2.51秒,但是这个时候屏幕上没有任何内容,因为Web字体还在加载中。
Web字体为什么会导致这种情况以及如何优化,不在本文的讨论范围
Blink的布局层逻辑根本不关心文字是否显示,但是字体是否显示对于用户体验却至关重要,所以在计算FMP时,应该把字体的可见性也考虑进去。
当布局发生时,如果有Web字体在加载中,那么应该延迟记录布局变动,直到字体加载完毕,但是需要设置3秒钟的超时时间,不然无限制的等下去也不行。但是如果把这个规则应用于所有Web字体有些过于激进,因为有一些icon字体其实也不是很重要,它不应该影响FMP的时间,所以最终选了200多个可以阻塞记录FMP时间的字符。现在计算FMP的准确率可以达到77%。
本文介绍了基于布局的方式捕获FMP的原理,现在我们应该明白使用工具捕获出的FMP到底是什么。
本质上这个数字并不是真正的FMP,它只是通过算法来猜测某个时间点可能是FMP,而这个时间点,是依靠布局对象的数量、意义、以及Web字体推算出来的,目前准确率可以达到77%。