在现场调试性能

了解如何使用调试信息归因性能数据,以便您通过分析来识别和解决实际用户问题

Google 提供了两类工具来衡量和调试性能:

  • 实验室工具:Lighthouse 等工具,可在模拟环境中加载网页,该环境可以模拟各种条件(例如,网络速度较慢和低端移动设备)。
  • 实测工具:例如 Chrome 用户体验报告 (CrUX),该报告基于 Chrome 提供的汇总的真实用户数据。(请注意,PageSpeed Insights 和 Search Console 等工具报告的实地数据来自 CrUX 数据。)

虽然实地测试工具可提供更准确的数据(实际反映真实用户体验的数据),但实验室工具通常更擅长帮助您发现和解决问题。

CrUX 数据更能代表网页的实际性能,但了解 CrUX 分数不太可能帮助您找出如何提升性能。

另一方面,Lighthouse 会发现问题,并针对如何改进提出具体建议。不过,Lighthouse 只会针对在网页加载时间发现的性能问题提出建议。它不会检测仅因用户互动(例如滚动或点击网页上的按钮)而显现的问题。

这就引出了一个重要问题:如何从实地真实用户那里捕获核心网页指标或其他性能指标的调试信息?

本文将详细介绍您可以使用哪些 API 来收集当前每个 Core Web Vitals 指标的其他调试信息,并为您提供有关如何在现有分析工具中捕获这些数据的想法。

用于归因和调试的 API

Cumulative Layout Shift (CLS)

在所有 Core Web Vitals 指标中,CLS 也许是最需要在实地收集调试信息的指标。CLS 是在整个网页生命周期内衡量的,因此用户与网页互动的方式(滚动距离、点击内容等)可能会对是否发生布局偏移以及哪些元素发生偏移产生重大影响。

请看以下 PageSpeed Insights 报告:

具有不同 CLS 值的 PageSpeed Insights 报告
PageSpeed Insights 会同时显示实地数据和实验室数据(如果可用),这两类数据可能会有所不同

实验室(Lighthouse)报告的 CLS 值与实际环境(CrUX 数据)中的 CLS 值相差很大,如果您考虑到网页可能包含许多在 Lighthouse 中测试时未使用的互动式内容,就会明白这是合理的。

但即使您了解用户互动会影响字段数据,您仍然需要知道网页上哪些元素发生了偏移,才导致第 75 百分位的分数达到 0.28。LayoutShiftAttribution 接口可实现此目的。

获取布局偏移归因

LayoutShiftAttribution 接口在 Layout Instability API 发出的每个 layout-shift 条目上公开。

如需详细了解这两个接口,请参阅调试布局偏移。就本文而言,您需要了解的主要内容是,作为开发者,您能够观察到网页上发生的每次布局偏移以及发生偏移的元素。

以下是一些示例代码,用于记录每次布局偏移以及发生偏移的元素:

new PerformanceObserver((list) => {
  for (const {value, startTime, sources} of list.getEntries()) {
    // Log the shift amount and other entry info.
    console.log('Layout shift:', {value, startTime});
    if (sources) {
      for (const {node, curRect, prevRect} of sources) {
        // Log the elements that shifted.
        console.log('  Shift source:', node, {curRect, prevRect});
      }
    }
  }
}).observe({type: 'layout-shift', buffered: true});

衡量并向分析工具发送每次布局偏移的数据可能不太实际;不过,通过监控所有偏移,您可以跟踪最严重的偏移,并仅报告这些偏移的相关信息。

我们的目标不是找出并修正每位用户遇到的每次布局偏移,而是找出影响最多用户的偏移,从而最大限度地降低网页第 75 百分位的 CLS。

此外,您无需在每次发生偏移时都计算最大的来源元素,只需在准备将 CLS 值发送到分析工具时才这样做。

以下代码会获取对 CLS 有贡献的 layout-shift 条目列表,并返回最大偏移中的最大来源元素:

function getCLSDebugTarget(entries) {
  const largestEntry = entries.reduce((a, b) => {
    return a && a.value > b.value ? a : b;
  });
  if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
    const largestSource = largestEntry.sources.reduce((a, b) => {
      return a.node && a.previousRect.width * a.previousRect.height >
          b.previousRect.width * b.previousRect.height ? a : b;
    });
    if (largestSource) {
      return largestSource.node;
    }
  }
}

确定导致最大偏移的最大元素后,您可以将该元素报告给数据分析工具。

对于给定的网页,对 CLS 贡献最大的元素可能会因用户而异,但如果您汇总所有用户的这些元素,则可以生成一份影响最多用户的位移元素列表。

在您确定并修正这些元素的偏移根本原因后,您的分析代码将开始报告较小的偏移,并将其视为网页的“最严重”偏移。最终,所有报告的偏移量都会足够小,使您的网页远低于“良好”阈值 0.1!

以下是一些可能有助于捕获最大位移源元素的相关元数据:

  • 最大偏移的时间
  • 发生最大变化时的网址路径(适用于动态更新网址的网站,例如单页应用)。

Largest Contentful Paint (LCP)

如需在实际环境中调试 LCP,您需要了解的主要信息是,在特定网页加载过程中,哪个特定元素是最大的元素(LCP 候选元素)。

请注意,即使是完全相同的网页,LCP 候选元素也很有可能因用户而异,事实上,这种情况非常常见。

以下是可能导致此问题的原因:

  • 用户设备的屏幕分辨率各不相同,这会导致网页布局各不相同,因此视口中可见的元素也会各不相同。
  • 用户并不总是加载滚动到最顶部的网页。链接通常会包含片段标识符,甚至包含文本片段,这意味着您的网页可能会在网页上的任何滚动位置加载和显示。
  • 内容可能会针对当前用户进行个性化设置,因此 LCP 候选元素可能会因用户而异。

这意味着,您无法假设特定网页上哪个元素或哪组元素将成为最常见的 LCP 候选元素。您必须根据真实用户行为来衡量。

识别 LCP 候选元素

如需在 JavaScript 中确定 LCP 候选元素,您可以使用 Largest Contentful Paint API,也就是用于确定 LCP 时间值的同一 API。

在观察 largest-contentful-paint 条目时,您可以通过查看最后一个条目的 element 属性来确定当前的 LCP 候选元素:

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];

  console.log('LCP element:', lastEntry.element);
}).observe({type: 'largest-contentful-paint', buffered: true});

确定 LCP 候选元素后,您可以将其与指标值一起发送到分析工具。与 CLS 类似,这有助于您确定哪些元素最需要优先优化。

除了 LCP 候选元素之外,衡量 LCP 子部分时间也可能很有用,这有助于确定哪些具体的优化步骤适合您的网站。

Interaction to Next Paint (INP)

在现场收集 INP 数据时,最重要的信息包括:

  1. 用户与哪个元素进行了互动
  2. 互动类型
  3. 相应互动发生的时间

导致互动缓慢的一个主要原因是主线程被阻塞,这种情况在 JavaScript 加载时很常见。了解大多数缓慢的互动是否发生在网页加载期间,有助于确定需要采取哪些措施来解决问题。

INP 指标会考虑互动的完整延迟时间,包括运行所有已注册的事件监听器所花费的时间,以及在所有事件监听器运行完毕后绘制下一帧所花费的时间。这意味着,对于 INP,了解哪些目标元素往往会导致互动缓慢,以及这些互动属于哪种类型,非常有用。

以下代码会记录 INP 条目的目标元素和时间。

function logINPDebugInfo(inpEntry) {
  console.log('INP target element:', inpEntry.target);
  console.log('INP interaction type:', inpEntry.name);
  console.log('INP time:', inpEntry.startTime);
}

请注意,此代码未显示如何确定哪个 event 条目是 INP 条目,因为该逻辑更为复杂。不过,以下部分介绍了如何使用 web-vitals JavaScript 库获取此信息。

与 web-vitals JavaScript 库搭配使用

前面的部分提供了一些常规建议和代码示例,用于捕获调试信息以包含在您发送给分析工具的数据中。

自版本 3 起,web-vitals JavaScript 库包含一个可显示所有这些信息的归因 build,以及一些其他信号。

以下代码示例展示了如何设置包含调试字符串的额外事件参数(或自定义维度),该字符串有助于确定性能问题的根本原因。

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'CLS':
      eventParams.debug_target = attribution.largestShiftTarget;
      break;
    case 'LCP':
      eventParams.debug_target = attribution.element;
      break;
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      break;
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

此代码是 Google Analytics 专用的,但其总体思路也应适用于其他分析工具。

此代码还仅展示了如何报告单个调试信号,但能够针对每个指标收集和报告多个不同的信号非常有用。

例如,为了调试 INP,您可能需要收集以下信息:互动元素、互动类型、时间、loadState、互动阶段以及更多信息(例如长动画帧数据)。

web-vitals 属性 build 会公开其他属性信息,如以下 INP 示例所示:

import {onCLS, onINP, onLCP} from 'web-vitals/attribution';

function sendToGoogleAnalytics({name, value, id, attribution}) {
  const eventParams = {
    metric_value: value,
    metric_id: id,
  }

  switch (name) {
    case 'INP':
      eventParams.debug_target = attribution.interactionTarget;
      eventParams.debug_type = attribution.interactionType;
      eventParams.debug_time = attribution.interactionTime;
      eventParams.debug_load_state = attribution.loadState;
      eventParams.debug_interaction_delay = Math.round(attribution.inputDelay);
      eventParams.debug_processing_duration = Math.round(attribution.processingDuration);
      eventParams.debug_presentation_delay =  Math.round(attribution.presentationDelay);
      break;

    // Additional metric logic...
  }

  // Assumes the global `gtag()` function exists, see:
  // https://developers.google.com/analytics/devguides/collection/ga4
  gtag('event', name, eventParams);
}

onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);

如需查看公开的调试信号的完整列表,请参阅 web-vitals 归因文档。

报告和直观呈现数据

开始收集调试信息以及指标值后,下一步就是汇总所有用户的数据,以便开始寻找规律和趋势。

如前所述,您不必解决用户遇到的所有问题,而应先解决影响用户数量最多的问题,这些问题也应该是对 Core Web Vitals 得分产生最大负面影响的问题。

对于 GA4,请参阅专门介绍如何使用 BigQuery 查询和直观呈现数据的文章。

摘要

希望本文能帮助您了解如何使用现有的性能 API 和 web-vitals 库来获取调试信息,从而根据实地真实用户的访问情况诊断性能问题。虽然本指南侧重于 Core Web Vitals,但其中的概念也适用于调试任何可在 JavaScript 中衡量的效果指标。

如果您是分析供应商,并且希望改进产品并向用户提供更多调试信息,不妨考虑本文介绍的一些技巧,但不要仅限于此处提供的想法。此博文旨在普遍适用于所有分析工具;不过,各个分析工具可能(也应该)捕获并报告更多调试信息。

最后,如果您认为由于 API 本身缺少功能或信息,导致您在调试这些指标时遇到困难,请发送电子邮件至 web-vitals-feedback@googlegroups.com,提供您的反馈。