找出 INP 较差的原因

对互动的响应能力不佳,会给用户留下页面运行缓慢甚至完全损坏的糟糕印象。Interaction to Next Paint (INP) 是一项基于 Event Timing API 的指标。它会衡量最差的互动(不包括一些离群值),以此作为网页响应速度的衡量指标。

找出网页无响应的根本原因可能很棘手,尤其是因为这取决于用户互动和环境条件(例如设备功能和网络条件)。与网页加载等更具可重复性和可预测性的场景相比,这使得诊断变得更加困难。实验室数据只能模拟一小部分真实用户场景,因此在实际环境中衡量 INP 缓慢的原因至关重要。

Event Timing API 允许将 INP 时长拆分为三个子部分:输入延迟(互动发生时已在进行的处理)、处理时长(互动直接导致的延迟)和呈现延迟(互动后渲染下一帧导致的延迟)。这有助于确定问题是出在其他代码、互动 JavaScript 代码还是浏览器处理渲染更新上,而不是直接跳到互动代码。

您可以进一步了解导致互动延迟的 JavaScript、样式重新计算和布局工作。使用 JS Self-Profiling API 的完整性能跟踪是一种重量级解决方案,很容易导致性能问题。长动画帧 API 是一种轻量级 API,可在现场使用,用于识别运行缓慢的 JavaScript,并区分 INP 互动期间的样式重新计算和布局开销:

  • 脚本级强制样式和布局(forcedStyleDuration 和 forcedLayoutDuration):报告在输入延迟或处理时长期间,正在运行的脚本中发生的同步样式重新计算(例如在 DOM 更改后调用 getComputedStyle())和强制布局/重排(例如在更改后读取 getBoundingClientRect() 或 offsetWidth)。
  • 帧级渲染样式和布局(styleDuration 和 layoutDuration):报告在帧的渲染更新期间,重新计算样式与更新布局所花费的时间,有助于解释呈现延迟时间较长的原因。

区分样式重新计算和布局至关重要,因为它们的根本原因和解决方法不同:样式时长过长表明 CSS 选择器匹配开销过高或样式失效范围过广(可通过简化选择器或缩小规则范围来解决),而布局时长过长则表明几何计算开销过高或同步布局抖动(可通过在写入前批量读取 DOM、简化 DOM 结构或使用 CSS 包含来解决)。

如何实现

通过原始 Event Timing API 计算 INP 非常复杂,并且存在一些细微差别。建议您使用 RUM 工具或库来收集这些数据。

web-vitals 是 Google 提供的一个开源库,用于计算 Core Web Vitals(包括 INP),还包含 INP 互动的子部分和长动画帧数据。

使用 web-vitals 库获取 INP 互动的归因

web-vitals 库是一个小型库,用于衡量 Core Web Vitals 和其他效果指标。onINP() 函数可用于识别最慢的互动,并包含有关 INP 子部分、互动期间执行的脚本以及使用长动画帧 API 的样式和布局持续时间的信息。

// Use the attribution build to get Long Animation Frame data
// alongside the INP metric value.
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  const { attribution } = metric;
  const longestScript = attribution.longestScript;

  // Sum rendering-phase style and layout durations across all LoAF entries
  // intersecting the INP interaction (primarily explains presentationDelay).
  let renderStyleDuration = 0;
  let renderLayoutDuration = 0;
  for (const entry of attribution.longAnimationFrameEntries) {
    renderStyleDuration += entry.styleDuration ?? 0;
    renderLayoutDuration += entry.layoutDuration ?? 0;
  }

  // Beacon script, style, and layout attribution for the INP interaction,
  // so you can identify the root cause in production.
  navigator.sendBeacon(
    '/analytics',
    JSON.stringify({
      name: 'INP',
      value: metric.value,
      // These fields give the INP subparts:
      inputDelay: attribution.inputDelay,
      presentationDelay: attribution.presentationDelay,
      processingDuration: attribution.processingDuration,
      interactionTarget: attribution.interactionTarget,
      // These fields identify which script function was responsible
      // for the longest processing during the INP interaction.
      invokerType: longestScript?.entry?.invokerType,
      sourceURL: longestScript?.entry?.sourceURL,
      sourceFunctionName: longestScript?.entry?.sourceFunctionName,
      sourceCharPosition: longestScript?.entry?.sourceCharPosition,
      // subpart indicates which phase (input delay, processing, or
      // presentation delay) the longest script overlapped with most.
      subpart: longestScript?.subpart,
      intersectingDuration: longestScript?.intersectingDuration,
      // Synchronous style and layout work forced inside the longest script
      // (with forcedStyleAndLayoutDuration as a combined fallback):
      forcedStyleDuration: longestScript?.entry?.forcedStyleDuration,
      forcedLayoutDuration: longestScript?.entry?.forcedLayoutDuration,
      forcedStyleAndLayoutDuration: longestScript?.entry?.forcedStyleAndLayoutDuration,
      // Rendering-phase style and layout work across the interaction's frames
      // (with totalStyleAndLayoutDuration as a combined fallback):
      renderStyleDuration,
      renderLayoutDuration,
      totalStyleAndLayoutDuration: attribution.totalStyleAndLayoutDuration
    })
  );
});

最佳做法

  • 务必先使用 INP 子部分来确定延迟是由于已经在运行 JavaScript(输入延迟)、互动的事件处理脚本 JavaScript(处理时长)还是后续渲染(呈现延迟)造成的。
  • 务必尝试找出造成最大阻塞的 JavaScript,并提供更多详细信息,尤其是输入延迟时间和处理时长。
  • 务必在诊断处理时长过长或呈现延迟时,区分样式重新计算(styleDuration、forcedStyleDuration)和布局(layoutDuration、forcedLayoutDuration):样式计算时间过长表示 CSS 选择器开销过大或失效范围过广,而布局时间过长则表示 DOM 几何结构存在瓶颈或同步布局抖动。
  • 务必优先使用 Long Animation Frames API 来提供这些更详细的信息,而不是使用 JS Self-Profiling API 等运行时开销更高的替代方案。
  • 务必在没有其他 RUM 解决方案的情况下使用 web-vitals 库。它可识别 INP 互动,并包含有关互动期间执行的子部分和脚本的信息(使用 Long Animation Frames API)。
  • 务必将所需信息信标回分析服务,而不仅仅是在本地记录。

浏览器支持和后备策略

大多数现代浏览器都支持 Event Timing API,该 API 是计算 INP 及其子部分所必需的。对于不支持此 API 的浏览器,无法衡量 INP。

Long Animation Frames API 提供了可选的其他详细信息。无需后备方案即可安全使用。在大多数情况下,它发现的性能提升机会也适用于其他浏览器。

如果您的基准目标支持 Long Animation Frames API (long-animation-frames),但不支持单独的样式和布局时长属性(PerformanceLongAnimationFrameTiming 上的 styleDuration 和 layoutDuration,或 PerformanceScriptTiming 上的 forcedStyleDuration 和 forcedLayoutDuration),请回退到组合样式和布局时间:

  • 在 PerformanceScriptTiming 上使用 script.forcedStyleAndLayoutDuration,以便在脚本中执行同步强制样式和布局工作。
  • 在 web-vitals/attribution 中使用 metric.attribution.totalStyleAndLayoutDuration(或在每个 PerformanceLongAnimationFrameTiming 条目上计算 (entry.paintTime || entry.startTime + entry.duration) - entry.styleAndLayoutStart),以实现样式和布局的组合。