在真实用户环境中调试网页性能

为性能数据附加调试信息,通过分析数据识别并修复真实用户遇到的问题。

Google 提供两类性能测量和调试工具:

现场工具的数据更能代表真实用户体验,实验室工具则通常更擅长帮助定位和修复问题。CrUX 分数反映实际性能,但只知道分数,未必能知道如何改进。

Lighthouse 会识别问题并给出具体建议,但它只针对页面加载期间发现的性能问题;滚动、点击按钮等用户交互才触发的问题,不在这类检测范围内。

因此,如何从真实用户访问中收集 Core Web Vitals 或其他性能指标的调试信息?本文介绍可用于各项核心指标的 API,以及如何把额外调试信息接入现有分析工具。

用于归因和调试的 API

累积布局偏移(CLS)

在所有核心指标中,CLS 可能最需要现场调试信息。它在页面整个生命周期内测量;用户滚动多远、点击什么,都会影响是否产生布局偏移,以及哪些元素移动。

看下面的 PageSpeed Insights 报告:

PageSpeed Insights 中实验室和现场 CLS 值不同
PageSpeed Insights 会在可用时同时显示现场和实验室数据,两者可能不同。

Lighthouse 与 CrUX 报告的 CLS 相差很大是合理的,因为页面可能包含大量 Lighthouse 测试时未触发的交互内容。但即使知道交互影响现场数据,仍需知道哪些元素发生移动,使第75百分位的分数达到0.28。LayoutShiftAttribution 可以提供这些信息。

获取布局偏移归因

Layout Instability API 发出的每个 layout-shift 条目都会暴露 LayoutShiftAttribution。两个接口的详细说明见调试布局偏移。关键是开发者能够观察页面中的每次偏移,以及移动的元素。

以下示例记录每次偏移及移动元素:

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

识别后就可以将元素上报分析工具。不同用户的主要贡献元素可能不同;汇总所有用户后,可以得到影响人数最多的移动元素列表。

修复这些元素的根因后,分析代码就会把更小的偏移报告为当前“最严重”偏移。最终,所有上报偏移都可以足够小,使页面处于0.1 的良好阈值以内。

除最大来源元素,还可收集最大偏移发生时间,以及当时的 URL 路径;后者特别适用于会动态更新 URL 的单页应用。

最大内容绘制(LCP)

现场调试 LCP 最重要的信息,是这次页面加载中哪个元素最大,即 LCP 候选元素。即使完全相同的页面,不同用户的候选元素也经常不同:

  • 设备屏幕分辨率不同,页面布局和视口内可见元素随之改变。
  • 用户不总从页面顶部开始。链接可能带片段标识符或文本片段,页面可能在任意滚动位置加载和显示。
  • 内容可能针对当前用户个性化。

因此不能想当然地认定某个或某组元素是常见 LCP 候选,必须基于真实用户行为测量。详情见实验室数据与现场数据为何不同。

识别 LCP 候选元素

可使用测量 LCP 时间值的同一个 Largest Contentful Paint API。观察 largest-contentful-paint 条目时,最后一个条目的 element 属性即当前候选:

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

确定候选元素后,把它与指标值一起上报。与 CLS 类似,这能帮助识别最值得优先优化的元素。还可以测量 LCP 各子阶段耗时,判断网站需要哪些具体优化。

交互到下一次绘制(INP)

更多现场调试方法见在现场找出慢交互。

INP 最重要的现场信息包括:交互的元素、交互类型,以及发生时间。慢交互的重要原因是主线程阻塞,尤其常见于 JavaScript 加载期间。知道慢交互是否主要发生在加载阶段,有助于选择修复方式。

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 库取得这些信息。

配合 web-vitals JavaScript 库使用

前面给出了收集额外调试信息并随指标上报的通用建议。自版本3起,web-vitals 提供 attribution 构建,暴露上述信息与其他归因信号。

以下通过额外的事件参数或自定义维度传送调试字符串,帮助找到性能问题根因:

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、各交互阶段耗时,以及长动画帧数据等。attribution 构建暴露了更多信息:

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 Report 工具入手,它已经支持核心指标的调试信息上报。

分析工具供应商可以使用这些技术改善产品,向用户提供更多调试信息,但无需局限于本文思路。本文旨在普遍适用于分析工具;具体工具能够且应该捕获、报告更丰富的细节。

如果 API 缺失的功能或信息妨碍指标调试,可以向 web-vitals-feedback@googlegroups.com 反馈。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容