Long Animation Frames API(LoAF 发音为 Lo-Af)是对 Long Tasks API 的更新,旨在更好地理解缓慢的界面 (UI) 更新。这有助于识别可能会影响下一次绘制的互动 (INP) Core Web Vitals 指标(衡量响应能力)的缓慢动画帧,或者识别其他影响流畅性的界面卡顿。
API 的状态
在从 Chrome 116 到 Chrome 122 进行源试用后,LoAF API 从 Chrome 123 开始发货。
背景:Long Tasks API
Long Animation Frames API 是 Long Tasks API 的替代品,该功能现已在 Chrome 中提供一段时间(自 Chrome 58 起)。顾名思义,Long Task API 可让您监控耗时较长的任务,即在主线程中占用 50 毫秒或更长时间的任务。可以使用具有 PeformanceObserver
的 PerformanceLongTaskTiming
接口监控长时间运行的任务:
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries());
});
observer.observe({ type: 'longtask', buffered: true });
耗时较长的任务可能会导致响应速度问题。如果用户尝试与页面交互(例如,点击按钮或打开菜单),但主线程已经在处理一项长时间运行的任务,则用户的交互会延迟,以等待该任务完成。
为了提高响应速度,我们通常建议拆分长时间运行的任务。如果将每个长任务分解为一系列多个较小的任务,则可以在这些任务之间执行更重要的任务,以避免在响应交互时出现明显延迟。
因此,在尝试提高响应能力时,首先需要运行性能跟踪并查看耗时较长的任务。这可以通过基于实验室的审核工具进行,例如 Lighthouse(它具有避免长时间的主线程任务审核),也可以在 Chrome 开发者工具中查看耗时较长的任务。
在确定响应性问题时,基于实验室的测试通常是一个糟糕的起点,因为这些工具可能不包含互动,但它们只是可能的互动的一小部分。理想情况下,您应衡量现场互动速度缓慢的原因。
Long Tasks API 的缺点
使用 Performance Observer 衡量实际工作中耗时较长的任务有点用。实际上,除了一个长期任务发生以及耗时多长时间之外,它并不能提供这么多信息。
真实用户监控 (RUM) 工具通常使用此信息来分析耗时较长的任务的数量或持续时间,或确定发生这些任务的网页,但其用途有限,无法提供导致耗时较长的任务的基本详情。Long Tasks API 只有基本归因模型,它最多只能告诉您发生耗时较长的任务的容器(顶级文档或 <iframe>
),而不是调用它的脚本或函数,如典型条目中所示:
{
"name": "unknown",
"entryType": "longtask",
"startTime": 31.799999997019768,
"duration": 136,
"attribution": [
{
"name": "unknown",
"entryType": "taskattribution",
"startTime": 0,
"duration": 0,
"containerType": "window",
"containerSrc": "",
"containerId": "",
"containerName": ""
}
]
}
Long Tasks API 也可能是不完整的视图,因为它可能也会排除一些重要任务。一些更新(例如渲染)发生在单独的任务中,理想情况下,这些更新应该与之前的执行一起包含,从而使该更新准确衡量“总工作”。要详细了解依赖任务存在的限制,请参阅“繁琐任务的不足之处”部分。
最后一个问题是,在衡量耗时较长的任务时,系统只会报告所用时间超过 50 毫秒限制的单个任务。一个动画帧可以由若干个小于该 50 毫秒限制的任务组成,但仍会共同阻止浏览器的呈现能力。
Long Animation Frames API
Long Animation Frames API (LoAF) 是一种新 API,旨在弥补 Long Tasks API 的一些缺点,使开发者能够获得更多富有实用价值的分析洞见,从而帮助解决响应能力问题并改进 INP。
良好的响应能力意味着网页能够快速响应通过它进行的交互。这涉及到能够及时绘制用户所需的任何更新,并避免阻止这些更新进行。对于 INP,建议响应时间不超过 200 毫秒,但对于其他更新(例如动画),即使响应时间可能过长,也建议完成。
Long Animation Frames API 是衡量阻塞工作的替代方法。顾名思义,Long Animation Frames API 衡量的是长动画帧,而不是单个任务。长动画帧是指渲染更新的延迟超过 50 毫秒(与 Long Tasks API 的阈值相同)。
观察较长的动画帧的方式与使用 PerformanceObserver
的耗时较长的任务类似,但应查看 long-animation-frame
类型:
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries());
});
observer.observe({ type: 'long-animation-frame', buffered: true });
您也可以从性能时间轴查询之前的长动画帧,如下所示:
const loafs = performance.getEntriesByType('long-animation-frame');
不过,性能条目存在 maxBufferSize
,之后较新的条目会被丢弃,因此建议使用 PerformanceObserver 方法。long-animation-frame
缓冲区大小设置为 200,与 long-tasks
相同。
查看帧而非任务的优势
从帧的角度而不是任务的角度来看,这个关键优势在于,一个长动画可以由任意数量的任务组成,这些任务累计生成一个长的动画帧。这解决了前面提到的最后一点,即 Long Tasks API 可能不会显示动画帧之前许多较小且阻塞渲染的任务的总和。
对于耗时较长的任务,这种替代视图的另一个优势是能够提供整个帧的时间细分。LoAF 并非像 Long Tasks API 一样只包含 startTime
和 duration
,而是包含帧时长各个部分的更详细的细分,包括:
startTime
:长动画帧的开始时间(相对于导航开始时间)。duration
:长动画帧的时长(不包括呈现时间)。renderStart
:渲染周期的开始时间,包括requestAnimationFrame
回调、样式和布局计算、调整观察器的大小以及 Intersection Observer 回调。styleAndLayoutStart
:样式和布局计算所用时间段的开始时间。firstUIEventTimestamp
:在此帧过程中要处理的第一个界面事件(鼠标/键盘等)的时间。blockingDuration
:动画帧被屏蔽的时长(以毫秒为单位)。
这些时间戳允许将长动画帧拆分为多个时间:
计时 | 计算 |
---|---|
开始时间 | startTime |
结束时间 | startTime + duration |
工作时长 | renderStart ? renderStart - startTime : duration |
渲染时长 | renderStart ? (startTime + duration) - renderStart: 0 |
渲染:布局前时长 | styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0 |
渲染:样式和布局时长 | styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0 |
如需详细了解各个计时,请参阅说明文档,其中详细说明了哪个 activity 促成了较长的动画帧。
改进归因
long-animation-frame
条目类型包含有助于较长动画帧的每个脚本的更优质的归因数据。
与 Long Tasks API 类似,这将在归因条目数组中提供,每个条目详细信息如下:
name
和EntryType
都将返回script
。- 一个有意义的
invoker
,指示脚本的调用方式(例如'IMG#id.onload'
、'Window.requestAnimationFrame'
或'Response.json.then'
)。 - 脚本入口点的
invokerType
:user-callback
:从网络平台 API 注册的已知回调(例如,setTimeout
、requestAnimationFrame
)。event-listener
:平台事件(例如click
、load
、keyup
)的监听器。resolve-promise
:平台 promise 的处理程序(例如fetch()
)。请注意,就 promise 而言,相同 promise 的所有处理程序会混合到一个“脚本”中”).
reject-promise
:与resolve-promise
相同,但针对遭拒的情况。classic-script
:脚本评估(例如<script>
或import()
)module-script
:与classic-script
相同,但适用于模块脚本。
- 为相应脚本提供单独的计时数据:
startTime
:条目函数被调用的时间。duration
:从startTime
到后续微任务队列完成处理之间的时长。executionStart
:编译后的时间。forcedStyleAndLayoutDuration
:在此函数内处理强制布局和样式所花费的总时间(请参阅抖动)。pauseDuration
:“暂停”所用的总时间同步操作(提醒、同步 XHR)。
- 脚本来源详细信息:
sourceURL
:脚本资源名称(如果可用;如果未找到,则为空)。sourceFunctionName
:脚本函数名称(如果可用;如果未找到,则为空)。sourceCharPosition
:脚本字符位置(若有;若未找到,则为 -1)。
windowAttribution
:出现长动画帧的容器(顶级文档或<iframe>
)。window
:对同源窗口的引用。
如果提供,源条目可让开发者确切了解长动画帧中每个脚本的调用方式,一直到调用脚本中的角色位置为止。这可提供造成较长动画帧的 JavaScript 资源的确切位置。
long-animation-frame
效果条目示例
完整的 long-animation-frame
性能条目示例(包含一个脚本)如下:
{
"blockingDuration": 0,
"duration": 60,
"entryType": "long-animation-frame",
"firstUIEventTimestamp": 11801.099999999627,
"name": "long-animation-frame",
"renderStart": 11858.800000000745,
"scripts": [
{
"duration": 45,
"entryType": "script",
"executionStart": 11803.199999999255,
"forcedStyleAndLayoutDuration": 0,
"invoker": "DOMWindow.onclick",
"invokerType": "event-listener",
"name": "script",
"pauseDuration": 0,
"sourceURL": "https://web.dev/js/index-ffde4443.js",
"sourceFunctionName": "myClickHandler",
"sourceCharPosition": 17796,
"startTime": 11803.199999999255,
"window": [Window object],
"windowAttribution": "self"
}
],
"startTime": 11802.400000000373,
"styleAndLayoutStart": 11858.800000000745
}
如您所见,这为网站提供了前所未有的数据量,以便了解呈现更新延迟的原因。
在现场使用 Long Animation Frames API
Chrome 开发者工具和 Lighthouse 等工具虽然有助于发现和重现问题,但它们属于实验室工具,它们可能会错过只有字段数据才能提供的重要用户体验方面。
Long Animation Frames API 旨在实际使用,以收集 Long Tasks API 无法用于收集用户互动的重要情境数据。这有助于您发现和重现原本可能未发现的互动问题。
功能检测 Long Animation Frames API 支持
您可以使用以下代码测试该 API 是否受支持:
if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
// Monitor LoAFs
}
链接到时间最长的 INP 互动
Long Animation Frames API 最明显的用例是帮助诊断和修复 Interaction to Next Paint (INP) 问题,这也是 Chrome 团队开发此 API 的主要原因之一。良好的 INP 是指从互动到帧绘制完成之间的所有互动在 200 毫秒或更短时间内得到响应,由于 Long Animation Frames API 会衡量所有耗时 50 毫秒或更长时间的帧,因此大多数有问题的 INP 应包含 LoAF 数据,以帮助您诊断这些互动。
“INP LoAF”是包含 INP 交互的 LoAF,如下图所示:
![网页上的较长动画帧示例,其中突出显示了 INP LoAF。](https://developer.chrome.google.cn/static/docs/web-platform/long-animation-frames/image/loaf-inp.png?authuser=6&hl=hu)
在某些情况下,INP 事件可能会跨越两个 LoAF - 通常,如果互动发生在帧开始渲染上一帧的部分之后,所以它在下一帧中处理的事件处理程序:
![网页上的较长动画帧示例,其中突出显示了 INP LoAF。](https://developer.chrome.google.cn/static/docs/web-platform/long-animation-frames/image/loaf-inp-two-frames.png?authuser=6&hl=hu)
在极少数情况下,甚至有可能超过两个 LoAF。
通过记录与 INP 互动相关的 LoAF 数据,您可以获取有关 INP 互动的更多信息,以便进行诊断。这对了解输入延迟特别有帮助,因为您可以看到该帧中运行的其他脚本。
如果您的事件处理脚本没有重现系统显示的值,那么了解无法解释的处理时长和呈现延迟时间也很有帮助,因为其他脚本可能正在为用户运行而这些脚本可能未包含在您自己的测试中。
没有直接 API 可将 INP 条目与其相关的 LoAF 条目相关联,但可以通过比较每个 INP 条目的开始和结束时间,在代码中实现这一点(请参阅 WhyNp 示例脚本)。
web-vitals
库在 v4 的 INP 归因接口的 longAnimationFramesEntries
属性中包含所有相交 LoAF。
关联一个或多个 LoAF 条目后,您可以添加 INP 归因的信息。scripts
对象包含一些最有价值的信息,因为它可以显示这些帧中的其他内容,因此通过信标将这些数据回发给您的分析服务,可让您详细了解互动缓慢的原因。
报告 INP 互动的 LoAF 是找出网页上最紧迫的互动问题的好方法。由于每位用户与您的网页互动的方式可能不同,而且如果 INP 归因数据足够多,INP 归因数据中将会包含许多潜在问题。这样,您就可以按数量对脚本进行排序,以便了解哪些脚本与慢速 INP 相关。
将更多长动画数据报告回分析端点
只关注 INP LoAF 的一个缺点是,您可能会错过其他潜在的改进空间,而这些方面可能会导致日后出现 INP 问题。这可能会导致一种追逐尾随的感觉,这时您修复了一个 INP 问题,并预期会有显著的改进,但最终发现下一个最慢的互动只比这个效果好一点,所以您的 INP 却没多大改善。
因此,您可能需要考虑整个网页生命周期中的所有 LoAF,而不是只关注 INP LoAF:
![具有许多 LoAF 的网页,其中一些 LoAF 发生在互动期间,即使不是 INP 互动也是如此。](https://developer.chrome.google.cn/static/docs/web-platform/long-animation-frames/image/all-loafs.png?authuser=6&hl=hu)
但是,每个 LoAF 条目都包含大量数据,因此您可能不希望所有操作都返回信标。相反,您需要将分析范围限制为某些 LoAF 或某些数据。
一些建议的模式包括:
以下哪一种模式最适合您,取决于您在优化过程中所处的阶段,以及常见的动画帧数有多长。对于之前从未针对响应能力进行优化的网站,您可能需要将许多 LoAF 限制为仅使用具有互动的 LoAF,或者设置较高的阈值,或者只查看最差的 LoAF。在解决常见的响应问题时,您可以进行扩展(不局限于互动和降低阈值),或者寻找特定模式。
观察较长的动画帧及互动情况
若要获取 INP 长动画帧之外的数据洞见,您可以查看所有具有互动(可以通过存在 firstUIEventTimestamp
值来检测)的 LoAF。
这也是一种更简单的监控 INP LoAF 的方法,而不是尝试将两者关联起来,后者可能更为复杂。在大多数情况下,这将包括指定访问的 INP LoAF;在极少数情况下,如果未显示 INP LoAF,它仍会显示需要修正的长时间互动,因为它们可能是其他用户的 INP 互动。
以下代码会记录帧内发生互动且时长超过 150 毫秒的所有 LoAF 条目。此处之所以选择 150,是因为它比 200 毫秒的“良好”略小INP 阈值。您可以根据需要选择较高或较低的值。
const REPORTING_THRESHOLD_MS = 150;
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.duration > REPORTING_THRESHOLD_MS &&
entry.firstUIEventTimestamp > 0
) {
// Example here logs to console, but could also report back to analytics
console.log(entry);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
观察时长超过特定阈值的动画帧
另一种策略是监控所有 LoAF,并将超过特定阈值的 LoAF 信标发送回分析端点,以供日后分析:
const REPORTING_THRESHOLD_MS = 150;
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.duration > REPORTING_THRESHOLD_MS) {
// Example here logs to console, but could also report back to analytics
console.log(entry);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
由于较长的动画帧条目可能会非常大,因此开发者应决定应将相应条目中的哪些数据发送到 Google Analytics。例如,条目的摘要时间(可能是脚本名称)或其他可能被视为必要的其他最低限度的其他上下文数据集合。
观察最长的动画帧数
网站可能希望收集最长动画帧(或多帧)数据,而不是设定阈值,以减少需要信标的数据量。因此,无论网页经历了多少个较长的动画帧,只有最差的一个、五个帧的数据,或者绝对必要的长动画帧数的数据才会被信标返回。
MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];
const observer = new PerformanceObserver(list => {
longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
(a, b) => b.blockingDuration - a.blockingDuration
).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });
这些策略也可以组合使用 - 仅查看互动时长超过 150 毫秒的 10 个最差的 LoAF。
在适当的时间(最好在 visibilitychange
事件发生)时信标返回数据分析。对于本地测试,您可以定期使用 console.table
:
console.table(longestBlockingLoAFs);
识别长动画帧中的常见模式
另一种策略是查看在长动画帧条目中出现最多的常见脚本。系统可能会在脚本和角色位置报告相关数据,以识别屡次违规者。
这可能特别适用于可自定义的平台,在这些平台上,可以在多个网站上识别会导致性能问题的主题或插件。
可以汇总并报告长动画帧中常见脚本(或第三方来源)的执行时间,以确定一个网站或一系列网站中长动画帧的常见原因。例如,查看网址的示例:
const observer = new PerformanceObserver(list => {
const allScripts = list.getEntries().flatMap(entry => entry.scripts);
const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
allScripts.filter(script => script.sourceURL === sourceURL)
]));
const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
sourceURL,
count: scripts.length,
totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
}));
processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
// Example here logs to console, but could also report back to analytics
console.table(processedScripts);
});
observer.observe({type: 'long-animation-frame', buffered: true});
此输出的示例如下:
(index) |
sourceURL |
count |
totalDuration |
---|---|---|---|
0 |
'https://example.consent.com/consent.js' |
1 |
840 |
1 |
'https://example.com/js/analytics.js' |
7 |
628 |
2 |
'https://example.chatapp.com/web-chat.js' |
1 |
5 |
在工具中使用 Long Animation Frames API
该 API 还允许使用其他开发者工具进行本地调试。虽然 Lighthouse 和 Chrome 开发者工具等一些工具能够使用较低级别的跟踪详细信息收集大部分此类数据,但拥有此较高级别的 API 可允许其他工具访问这些数据。
在开发者工具中显示长动画帧数据
您可以使用 performance.measure()
API 在开发者工具中显示较长的动画帧,这些帧随后会显示在开发者工具用户计时轨道中的性能跟踪记录中,以表明您需要集中精力提升性能:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
performance.measure('LoAF', {
start: entry.startTime,
end: entry.startTime + entry.duration,
});
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
从长远来看,它很可能会整合到开发者工具中,但之前的代码段允许它同时显示在开发者工具中。
在其他开发者工具中使用长动画帧数据
Web Vitals 扩展程序已在日志记录摘要调试信息中显示相应值,以诊断性能问题。
现在,它还会显示每个 INP 回调和每次互动的长动画帧数据:
![Web Vitals Extension 控制台日志记录。](https://developer.chrome.google.cn/static/docs/web-platform/long-animation-frames/image/loaf-web-vitals-extension.png?authuser=6&hl=hu)
在自动化测试工具中使用长动画帧数据
同样,CI/CD 流水线中的自动化测试工具可以通过在运行各种测试套件时测量较长的动画帧来揭示潜在性能问题的详细信息。
常见问题解答
以下是有关此 API 的一些常见问题解答:
为何不干脆扩展或迭代 Long Tasks API 呢?
这是另一种方法,用于报告与潜在响应问题类似但最终却有所不同的衡量结果。请务必确保依赖现有 Long Tasks API 的网站继续正常运行,以免干扰现有用例。
尽管 Long Tasks API 可能受益于 LoAF 的部分功能(例如更好的归因模型),但我们认为,专注于帧而非任务具有许多优势,这使其成为与现有的 Long Tasks API 完全不同的 API。
为什么我没有脚本条目?
这可能表明,动画帧较长并非由 JavaScipt 所致,而是渲染工作量较大。
如果动画帧较长是由 JavaScript 导致的,但出于上述各种隐私权原因(主要是 JavaScript 不归相关网页所有)而无法提供脚本属性,也可能会出现上述情况。
为什么我有脚本条目,但没有来源信息或来源信息有限?
造成这种情况的原因有很多,包括没有合适的来源可供参考。
no-cors cross-origin
脚本的脚本信息也会受到限制,不过可以通过将 crossOrigin = "anonymous"
添加到 <script>
调用,使用 CORS 提取这些脚本来解决此问题。
例如,要添加到页面的默认 Google 跟踪代码管理器脚本:
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
可以增强以添加 j.crossOrigin = "anonymous"
,从而为 GTM 提供完整的归因详情
该功能会取代 Long Tasks API 吗?
虽然我们相信 Long Animation Frames API 是一种更好、更完整的 API,用于测量耗时较长的任务,但目前还没有计划弃用 Long Tasks API。
期待反馈
您可以通过 GitHub 问题列表提供反馈,也可以在 Chrome 的问题跟踪器中报告 Chrome 实现 API 时出现的错误。
总结
Long Animation Frames API 是一个激动人心的新 API,与之前的 Long Tasks API 相比具有许多潜在优势。
事实证明,它是解决 INP 衡量的响应能力问题的重要工具。INP 是一个具有挑战性的优化指标,而 Chrome 团队希望通过该 API 来让开发者更轻松地发现和解决问题。
不过,Long Animation Frames API 并不局限于 INP,它可以帮助确定更新缓慢的其他原因,这些原因可能会影响网站用户体验的整体流畅度。