Long Animation Frames API

Long Animation Frames API(LoAF,发音为 Lo-Af)是对 Long Tasks API 的更新,旨在让您更好地了解缓慢的界面 (UI) 更新。这有助于识别可能会影响下一次绘制 (Interaction to Next Paint, INP) 核心 Web 指标(用于衡量响应速度)的慢动画帧,或识别其他会影响流畅性的界面卡顿。

API 的状态

浏览器支持

  • 123
  • x
  • x
  • x

从 Chrome 116 到 Chrome 122 进行源试用后,LoAF API 已从 Chrome 123 中推出

Long Tasks API

浏览器支持

  • 58
  • 79
  • x
  • x

来源

Long Animation Frames API 是 Long Tasks API 的替代方案,目前已在 Chrome 中推出(从 Chrome 58 开始)。顾名思义,Long Task API 可让您监控耗时较长的任务,即占用主线程 50 毫秒或更长时间的任务。您可以使用 PerformanceLongTaskTiming 接口通过 PeformanceObserver 监控耗时较长的任务:

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

浏览器支持

  • 123
  • x
  • x
  • x

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 那样只包含 startTimeduration,而是包含对帧时长各个部分的详细细分,其中包括:

  • 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 类似,该 API 将以归因条目数组的形式提供,其中每个条目的详细信息如下:

  • nameEntryType 都将返回 script
  • 有意义的 invoker,指示脚本的调用方式(例如 'IMG#id.onload''Window.requestAnimationFrame''Response.json.then')。
  • 脚本入口点的 invokerType
    • user-callback:通过 Web 平台 API 注册的已知回调(例如 setTimeoutrequestAnimationFrame)。
    • event-listener:平台事件(例如 clickloadkeyup)的监听器。
    • 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 123 开始,Long Animation Frames API 默认处于启用状态。

在现场使用 Long Animation Frames API

Lighthouse 等工具虽然有助于发现和重现问题,但它们属于实验室工具,可能会错过只有实测数据才能提供的用户体验的重要方面。可以在现场使用 Long Animation Frames API 为用户互动收集重要的上下文数据,而 Long Tasks API 无法做到这一点。这有助于您发现和重现您通过其他方式可能没有发现的互动问题。

下面列出了一些建议的策略,Chrome 团队非常希望了解对此 API 的反馈,以及开发者和 RUM 提供商如何使用该 API 提供的信息。

功能检测 Long Animation Frames API 支持

您可以使用以下代码测试该 API 是否受支持:

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

在这种情况下,如果系统目前不支持长动画帧,并且处于此过渡状态,则可以使用以下替代方案:

if ('PerformanceLongAnimationFrameTiming' in window) {
  // Monitor LoAFs
}

将长动画数据报告回分析端点

如上所示,LoAF 性能条目包含有价值的信息。一种策略是监控所有 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 });

由于较长的动画帧条目可能非常大,因此开发者应决定应将条目中的哪些数据发送到分析功能。例如,条目的摘要时间(可能是脚本名称),或一些其他可能被认为是必要的最少量其他上下文相关数据。

观察到最差的长动画帧

网站可能需要收集有关最长动画帧(或多个帧)的数据,以减少需要信标的数据量。因此,无论页面经历了多少个长动画帧,系统都只会以信标方式返回最差的 1、5 或绝对必要的长动画帧的数据。

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 });

在适当的时间(理想情况下,在 visibilitychange 事件发生)将信标返回给分析。对于本地测试,您可以定期使用 console.table

console.table(longestBlockingLoAFs);

链接到最长的 INP 互动

作为观察最差 LoAF 的扩展,与 INP 条目对应的 LoAF 帧可用作归因数据,以进一步提供有关如何改进 INP 的细节。

目前没有直接 API 可将 INP 条目与其相关的一个或多个 LoAF 条目相关联,但您可以在代码中通过比较每个条目的开始时间和结束时间来关联 INP 条目(请参阅此示例脚本)。

报告包含互动的长动画帧数

另一种需要较少代码的方法是,始终发送在帧中发生互动的最大(或前 X 大)LoAF 条目(可通过是否存在 firstUIEventTimestamp 值来检测)。在大多数情况下,这将包括指定访问的 INP 互动;在极少数情况下,如果没有 INP 互动,它仍会显示需要修正的长时间互动,因为这些互动可能是其他用户的 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 });

识别长动画帧中的常见模式

另一种策略是查看较长的动画帧条目中出现的常见脚本。可在脚本和/或字符位置级别发回数据,以识别屡次违规者。

这对于可自定义的平台尤其有效,在这些平台上,可以更轻松地在多个网站上确定导致性能问题的主题或插件。

对长动画帧中常见脚本(或第三方来源)的执行时间进行求和并发回报告,以找出整个网站或一组网站中长动画帧的常见原因。例如,要查看网址:

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 });

如果事实证明此 API 从长远来看很有用,它可能会并入开发者工具本身,但之前的代码段允许其同时出现在那里。

在其他开发者工具中使用长动画帧数据

Web Vitals 扩展程序显示了日志记录摘要调试信息中的值,可用于诊断性能问题。现在,该 API 已发布,此类工具可以更轻松地获取数据,帮助开发者了解应该将工作重心放在哪些方面。我们还计划在版本 4 中将其添加到 Web Vitals JavaScript 库

在自动化测试工具中使用长动画帧数据

同样地,自动化测试工具(可能在 CI/CD 流水线中)可以通过在运行各种测试套件时测量较长的动画帧,来显示潜在性能问题的细节。

常见问题解答

以下是有关此 API 的一些常见问题解答:

为什么不只是扩展或迭代 Long Tasks API?

这也是一种报告,以类似但最终不同的方式报告潜在的响应性问题。请务必确保依赖现有 Long Tasks API 的网站可继续正常运行,以免干扰现有用例。

虽然 Long Tasks API 可能会受益于 LoAF 的某些功能(例如更好的归因模型),但我们认为,专注于帧而不是任务具有许多优势,这使得该 API 成为与现有 Long Tasks API 截然不同的 API。

它会取代 Long Tasks API 吗?

虽然我们认为 Long Animation Frames API 是衡量耗时较长的 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,它可以帮助确定导致更新缓慢的其他原因,这些原因会影响网站的整体流畅性。

致谢

缩略图,作者:Henry Be,选自 Unsplash 网站。