现在是成为网页性能爱好者的绝佳时机,而 Chrome 60 中 Paint Timing API 的出现就是明证。Paint Timing API 是新兴的 性能 API 的又一重要补充,但它并非用于捕获 页面 和 资源 的时间,而是 这个全新且实验性的 API 使您可以捕获页面开始绘制时的指标。
如果您从未尝试过任何性能 API,那么您可能需要先了解一下它们,因为此 API 的语法与那些 API 非常相似(特别是 Resource Timing API)。也就是说,即使您不了解,也可以继续阅读本文并有所收获。不过,在我们深入了解之前,让我们谈谈绘制以及此 API 收集的特定时间指标。
为什么我们需要一个 API 来测量绘制时间?
如果您正在阅读本文,您可能已经熟悉绘制的概念。如果不是,它很容易理解:绘制是指浏览器执行的任何涉及将像素绘制到浏览器窗口的操作。它是渲染管道中的一个重要部分。当我们在性能术语中谈论绘制时,我们通常指的是浏览器开始绘制页面的时间,因为页面正在加载。这个时刻被恰当地称为“首次绘制时间”。
为什么这个指标如此重要?因为它标志着用户请求页面后,某些东西最早出现的时刻。页面加载过程中会发生很多事情,但有一点我们知道,就是越早让用户看到某些东西,他们就越早意识到正在发生事情。有点像您的 LDL 胆固醇,大多数与性能相关的目标都是要降低数值。但是,在您了解初始数值之前,达到这些目标可能会徒劳无功。
值得庆幸的是,Paint Timing API 可以帮我们解决这个问题。这个 API 使您能够使用 JavaScript 捕获页面为您的网站访问者绘制的速度。在诸如 Lighthouse 或 sitespeed.io 等程序中进行的合成测试非常棒,因为它为我们提供了改进我们管理网站性能的基准,但所有这些测试都在真空中进行。它不会告诉您您的网站对实际使用它的人来说表现如何。
与类似的性能 API 相比,Paint Timing API 要简单得多。它只为我们提供了两个指标
first-paint
:这很可能就是您所想到的意思。浏览器在页面上绘制第一个像素的时刻。它可能看起来像这样

first-paint
可能的样子。first-contentful-paint
:这与 first-paint
有所不同,因为它捕获第一个内容绘制的时间,无论是文本、图像还是任何非内容样式的变体。这种情况可能看起来像这样

first-contentful-paint
事件可能的样子。重要的是要注意,这两个时间点可能并不总是如此不同。根据特定网站的客户端架构,first-paint
和 first-contentful-paint
指标可能不会有所区别。在需要更快、更轻量级网页体验的情况下,它们通常几乎相同(甚至完全相同)。在客户端架构包含大量资产的大型网站上(或当连接速度较慢时),这两个指标可能相隔更远。
无论如何,让我们看看如何使用已在 Chrome 60 中推出的这个 API。
一个简单的用例
您可以通过几种方法使用此 API。最简单的方法是将代码附加到首次绘制后发生的一些事件。您可能希望将其附加到事件而不是立即运行它的原因是,这样当您尝试从 API 中提取指标时,它们实际上可用。例如,看这段代码
if("performance" in window){
window.addEventListener("load", ()=>{
let paintMetrics = performance.getEntriesByType("paint");
if(paintMetrics !== undefined && paintMetrics.length > 0){
paintMetrics.forEach((paintMetric)=>{
console.log(`${paintMetric.name}: ${paintMetric.startTime}`);
});
}
});
}
这段代码执行以下操作
- 我们进行一个简单的检查,以查看
performance
对象是否在window
对象中。这可以防止我们的任何代码在performance
不可用时运行。 - 我们使用
addEventListener
将代码附加到window
对象的load
事件,该事件将在页面及其资产完全加载时触发。 - 在
load
事件代码中,我们使用performance
对象的getEntriesByType
方法将所有"paint"
事件类型检索到一个名为paintMetrics
的变量中。 - 由于只有 Chrome 60(及更高版本)当前实现了 Paint Timing API,因此我们需要检查是否返回了任何条目。为此,我们检查
paintMetrics
是否为undefined
并且它的length
是否大于0
。 - 如果我们通过了这些检查,我们就会将指标的名称及其开始时间输出到控制台中,这将看起来像这样

您在上面的控制台屏幕截图中看到的计时是以毫秒为单位的。从这里,您可以将这些指标发送到某个地方进行存储和分析,以便以后使用。
这很好,但是如果我们想要在浏览器收集到这些指标后立即访问它们呢?为此,我们需要 PerformanceObserver
。
PerformanceObserver
捕获绘制时间指标
使用 如果您绝对需要在浏览器中获得这些指标后立即访问它们,那么可以使用 PerformanceObserver
。使用 PerformanceObserver
可能很棘手,尤其是如果您想确保自己不会破坏不支持它的浏览器的行为,或者即使浏览器支持它,但也不支持 "paint"
事件。后一种情况与我们在这里的努力密切相关,因为轮询不支持的事件可能会抛出 TypeError
。
由于 PerformanceObserver
会异步收集指标并记录它们,因此我们最好的选择是使用 Promise,它可以帮助我们在没有昔日回调地狱的情况下处理异步操作。例如,看这段代码
if("PerformanceObserver" in window){
let observerPromise = new Promise((resolve, reject)=>{
let observer = new PerformanceObserver((list)=>{
resolve(list);
});
observer.observe({
entryTypes: ["paint"]
});
}).then((list)=>{
list.getEntries().forEach((entry)=>{
console.log(`${entry.name}: ${entry.startTime}`);
});
}).catch((error)=>{
console.warn(error);
});
}
让我们详细了解这段代码
- 我们检查
window
中是否存在PerformanceObserver
对象。如果PerformanceObserver
不存在,则不会发生任何事情。 - 创建了一个
Promise
。在 Promise 链的第一部分,我们创建一个新的PerformanceObserver
对象,并将其存储在observer
变量中。这个观察者包含一个回调,它会使用绘制时间指标列表来resolve
Promise。 - 我们需要从某处获取这些绘制时间指标,对吧?这就是
observer
方法发挥作用的地方。这个方法允许我们定义我们想要哪些类型的性能条目。由于我们想要绘制事件,因此我们只需传入一个包含"paint"
事件类型的数组。 - 如果浏览器支持使用
PerformanceObserver
收集"paint"
事件,则 Promise 会解析,并且链的下一部分会启动,在那里我们随后可以访问list
变量的getEntries
方法提供的条目。这将生成与之前示例非常相似的控制台输出。 - 如果当前浏览器不支持使用
PerformanceObserver
收集"paint"
事件,则catch
方法会提供对错误消息的访问权限。从这里,我们可以对这些信息做任何我们想做的事情。
现在您有了异步收集指标的方法,而不是必须等待页面加载。我个人更喜欢之前的方法,因为代码更简洁、更易读(至少对我来说是这样)。我确信我的方法不是最健壮的,但它们说明了您可以在浏览器中以可预测的方式收集绘制时间指标,这在旧版浏览器中不会抛出错误。
我将如何使用它?
这取决于你想做什么。也许你想看看你的网站在真实用户环境下的渲染速度。也许你想收集数据进行研究。在撰写本文时,我正在进行一个图像质量研究项目,评估参与者对 JPEG 和 WebP 图像有损图像质量的感知。作为研究的一部分,我使用其他计时 API 收集性能相关信息,但我同时也在收集绘制计时数据。我不知道这些数据是否有用,但与其他指标一起收集和分析它们可能对我的研究结果有所帮助。然而,你如何使用这些数据完全取决于你。我认为,这个 API 的存在非常棒,我希望更多浏览器尽快实现它。
其他你可能想阅读的内容
阅读这篇简短的文章可能让你对更广泛的性能接口的其他部分感兴趣。如果你对此感到好奇,这里有一些文章可以供你参考。
- 这个 API 的表面与已有的资源计时 API共享,所以你应该对此进行了解。如果你对文章中的代码感到满意,你将能够立即从这个非常有价值的 API 中获益。
- 虽然这个 API 与导航计时 API没有太多共同之处,但你真的应该阅读它。这个 API 允许你收集有关 HTML 本身加载速度的计时数据。
PerformanceObserver
的功能远不止我在这里演示的。你可以使用它来获取资源计时和用户计时。在这里了解它.- 说到用户计时,有一个专门的 API。使用这个 API,你可以用高精度的 timestamps 测量特定 JavaScript 任务执行的时间。你也可以使用这个工具来测量用户与页面交互的延迟。
现在你已经体验了这个 API,那就开始动手尝试吧,看看它(和其他 API)能为你做什么,帮助你让网页对用户更快!
规范如何区分首次绘制和首次内容绘制?它如何确定内容?
根据https://github.com/WICG/paint-timing
"first-paint"
条目包含一个 DOMHighResTimeStamp,它报告浏览器在导航后首次渲染的时间。这排除了默认背景绘制,但包括非默认背景绘制。这是开发者在页面加载过程中关注的第一个关键时刻——当浏览器开始渲染页面时。"first-contentful-paint"
包含一个 DOMHighResTimestamp,它报告浏览器首次渲染任何文本、图像(包括背景图像)、非白色画布或 SVG 的时间。这包括带有待处理网页字体的文本。这是用户可以开始消费页面内容的第一次。希望这有帮助!
Jeremy,很棒的介绍文章!
我假设这两个解决方案都将在 Chrome 60+ 及其他浏览器实现这些 API 时生效,所以问题是——这两个解决方案在浏览器支持方面是否相同,或者其中一个比另一个更受欢迎?
谢谢。
这真的取决于你想实现什么:你想尽快收集指标并在它们可用时立即采取行动吗?使用
PerformanceObserver
。如果你能负担等待,请在load
(或类似事件)中收集这些指标。我认为前者更容易编写和理解,这就是我在我的图像调查网站上收集指标时采用的方法。任何支持资源/绘制计时 API 的浏览器都可能支持PerformanceObserver
,但使用addEventListener
附加事件得到广泛支持。祝你好运!