在现场调试性能

了解如何使用调试信息对性能数据进行归因,从而帮助您通过 Google Analytics 识别和解决实际用户问题

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

  • 实验室工具:Lighthouse 等工具,可在模拟环境中加载您的网页,模拟各种条件(例如网络速度缓慢和使用低端移动设备)。
  • 现场工具Chrome 用户体验报告 (CrUX) 等工具,该工具基于来自 Chrome 的汇总真实用户数据。(请注意,PageSpeed InsightsSearch 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 候选元素。您必须根据实际用户行为来衡量效果。

确定 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,该 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 中可衡量的任何性能指标。

如果您刚刚开始衡量性能,并且已经是 Google Analytics 用户,不妨先使用 Web Vitals 报告工具,因为该工具已支持报告 Core Web Vitals 指标的调试信息。

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

最后,如果您感觉由于 API 本身缺少功能或信息而无法调试这些指标,请向 web-vitals-feedback@googlegroups.com 发送反馈。