了解如何使用调试信息对性能数据进行归因,以帮助您通过分析识别和修复实际用户问题
Google 提供了两类工具来衡量和调试性能:
- 实验室工具:Lighthouse 等工具。在此类工具中,您的页面会在可以模拟各种条件(例如网速缓慢和低端移动设备)的模拟环境中加载。
- 实测工具:Chrome 用户体验报告 (CrUX) 等工具,这些工具基于来自 Chrome 的汇总真实用户数据。(请注意,PageSpeed Insights 和 Search Console 等工具报告的字段数据来自 CrUX 数据。)
虽然现场工具可提供更准确的数据(即实际代表真实用户体验的数据),但实验室工具通常能够更好地帮助您识别和解决问题。
CrUX 数据更能代表网页的实际性能,但了解您的 CrUX 得分不太可能帮助您找出如何提高性能。
另一方面,Lighthouse 将发现问题并提出具体的改进建议。不过,Lighthouse 只会针对其在网页加载时发现的性能问题提出建议。它不会检测仅因用户互动(例如滚动或点击页面上的按钮)而出现的问题。
这就引出了一个重要的问题:如何从实际用户的那里捕获核心网页指标的调试信息或其他性能指标?
这篇博文详细介绍了您可以使用哪些 API 为当前的每项 Core Web Vitals 指标收集其他调试信息,并就如何在现有分析工具中捕获这些数据提供思路。
用于归因和调试的 API
CLS
在所有 Core Web Vitals 指标中,CLS 可能是最重要的指标,因为这项指标在现场收集调试信息可能最为重要。CLS 会在网页的整个生命周期内衡量,因此用户与网页的互动方式(滚动多远、点击的内容等)会对是否发生布局偏移以及哪些元素发生变化产生重大影响。
以 PageSpeed Insights 的以下报告为例:
实验室 (Lighthouse) 报告的 CLS 值与实地报告的 CLS 值(CrUX 数据)有很大差异。如果您认为页面可能包含在 Lighthouse 中测试时可能并未使用的大量互动内容,那么这种做法是合理的。
但即使您了解用户互动会影响字段数据,您仍然需要知道页面上的哪些元素会发生变化,从而得出第 75 百分位的得分为 0.3。
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});
对于发生的每次布局偏移,衡量数据并将其发送到分析工具可能不太现实;不过,通过监控所有偏移,您可以跟踪最糟糕的变化并只报告这些偏移的相关信息。
我们的目标不是确定和修复针对每位用户发生的每一次布局偏移,而是确定影响用户最多且因此对页面的 CLS 影响最大的变化(位于第 75 百分位)。
此外,您不需要在每次发生变化时计算最大的源元素,只需在准备好将 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)!
以下是与最大偏移源元素一起捕获的其他一些元数据:
- 最明显变化的时间
- 发生最大幅度变化时的网址路径(适用于动态更新网址的网站,例如单页应用)。
LCP
若要在该字段中调试 LCP,您需要主要信息是哪个特定元素是该特定网页加载的最大元素(LCP 候选元素)。
请注意,LCP 候选元素因用户而异是完全有可能的(实际上很常见),即使对于同一个网页,LCP 候选元素也可能不同。
以下是可能导致此问题的原因:
- 用户设备的屏幕分辨率不同,这会导致不同的页面布局,进而在视口内显示不同的元素。
- 用户并非总是加载滚动到最顶端的网页。通常,链接会包含 fragment 标识符,甚至包含文本片段,这意味着您的网页可以在页面上的任何滚动位置加载并显示。
- 内容可能是针对当前用户进行个性化调整的,因此 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 子部分时间可能也很有用,这对于确定与您的网站相关的特定优化步骤非常有用。
FID
如需在字段中调试 FID,请务必注意,FID 仅测量整体第一个输入事件延迟时间的延迟部分。这意味着,与用户交互的内容并不像他们交互时主线程上发生的其他事件那么重要。
例如,许多支持服务器端呈现 (SSR) 的 JavaScript 应用都会提供静态 HTML,此类 HTML 可在屏幕与用户输入交互之前(即,使内容变为互动式所需的 JavaScript 完成加载之前)呈现到屏幕上。
对于这些类型的应用,了解首次输入是发生在水合之前还是之后非常重要。如果发现许多人会在水合完成之前尝试与网页互动,请考虑将网页呈现为已停用或正在加载状态,而不是处于互动状态。
如果应用框架公开了水化合时间戳,您可以将其与 first-input
条目的时间戳进行比较,以确定首次输入是发生在水合之前还是之后。如果您的框架不显示该时间戳,或者根本不使用水合,则另一个有用的信号可能是输入是在 JavaScript 加载之前还是之后发生。
DOMContentLoaded
事件会在网页的 HTML 完全加载并解析后触发,其中包括等待加载任何同步脚本、延迟脚本或模块脚本(包括所有静态导入的模块)。因此,您可以使用该事件的时间,并将其与发生 FID 的时间进行比较。
以下代码会观察 first-input
条目,并记录第一个输入是否发生在 DOMContentLoaded
事件结束之前:
new PerformanceObserver((list) => {
const fidEntry = list.getEntries()[0];
const navEntry = performance.getEntriesByType('navigation')[0];
const wasFIDBeforeDCL =
fidEntry.startTime < navEntry.domContentLoadedEventStart;
console.log('FID occurred before DOMContentLoaded:', wasFIDBeforeDCL);
}).observe({type: 'first-input', buffered: true});
识别 FID 目标元素和事件类型
其他可能有用的调试信号包括互动过的元素及其互动类型(例如 mousedown
、keydown
、pointerdown
)。虽然与元素本身的互动不影响 FID(请注意,FID 只是总事件延迟中的延迟部分),了解用户与哪些元素互动可能有助于确定如何以最佳方式改进 FID。
例如,如果用户的绝大多数首次互动都是与特定元素进行的,请考虑在 HTML 中内嵌该元素所需的 JavaScript 代码,并延迟加载其余元素。
如需获取与第一个输入事件相关联的互动类型和元素,您可以引用 first-input
条目的 target
和 name
属性:
new PerformanceObserver((list) => {
const fidEntry = list.getEntries()[0];
console.log('FID target element:', fidEntry.target);
console.log('FID interaction type:', fidEntry.name);
}).observe({type: 'first-input', buffered: true});
INP
INP 与 FID 非常相似,因为要在现场捕获的最有用的信息位是:
- 用户与哪个元素进行了互动
- 为什么是互动类型
- 互动发生的时间
与 FID 一样,导致交互缓慢的主要原因是主线程被阻塞,这在加载 JavaScript 时很常见。了解大多数在网页加载期间是否存在缓慢的互动有助于确定需要采取什么措施来解决问题。
与 FID 不同,INP 指标会考虑互动的完整延迟时间,包括运行任何已注册的事件监听器所用的时间,以及在所有事件监听器运行后绘制下一帧所用的时间。这意味着,对于 INP 来说,了解哪些目标元素往往会导致互动缓慢,以及这些是哪些类型的互动更为有用。
由于 INP 和 FID 均基于 Event Timing API,因此在 JavaScript 中确定此信息的方式与上一个示例非常相似。以下代码会记录 INP 条目的目标元素和时间(相对于 DOMContentLoaded
)。
function logINPDebugInfo(inpEntry) {
console.log('INP target element:', inpEntry.target);
console.log('INP interaction type:', inpEntry.name);
const navEntry = performance.getEntriesByType('navigation')[0];
const wasINPBeforeDCL =
inpEntry.startTime < navEntry.domContentLoadedEventStart;
console.log('INP occurred before DCL:', wasINPBeforeDCL);
}
请注意,此代码未展示如何确定哪个 event
条目是 INP 条目,因为该逻辑更复杂。不过,以下部分介绍了如何使用 web-vitals JavaScript 库获取这些信息。
使用 web-vitals JavaScript 库
上文提供了一些常规建议和代码示例,用于捕获要在您发送到分析工具的数据中包含的调试信息。
从版本 3 开始,web-vitals JavaScript 库包含一个归因 build,用于显示所有这些信息,以及一些其他信号。
以下代码示例展示了如何设置包含调试字符串的额外事件参数(或自定义维度),该字符串有助于确定性能问题的根本原因。
import {onCLS, onFID, 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 'FID':
case 'INP':
eventParams.debug_target = attribution.eventTarget;
break;
}
// Assumes the global `gtag()` function exists, see:
// https://developers.google.com/analytics/devguides/collection/ga4
gtag('event', name, eventParams);
}
onCLS(sendToGoogleAnalytics);
onLCP(sendToGoogleAnalytics);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);
这段代码是 Google Analytics(分析)专用的代码,但总体思路也应该转化为其他分析工具。
这段代码也只是展示了如何报告单个调试信号,不过,如果能针对每个指标收集和报告多个不同的信号,可能就会很有用。例如,如需调试 INP,您可能需要收集互动类型、时间以及用户与之互动的元素。web-vitals
归因 build 会显示所有这些信息,如以下示例所示:
import {onCLS, onFID, 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.eventTarget;
eventParams.debug_type = attribution.eventType;
eventParams.debug_time = attribution.eventTime;
eventParams.debug_load_state = attribution.loadState;
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);
onFID(sendToGoogleAnalytics);
onINP(sendToGoogleAnalytics);
如需查看公开的调试信号的完整列表,请参阅 Web Vitals 归因文档。
报告并直观呈现数据
开始收集调试信息以及指标值后,下一步就是汇总所有用户的数据,以开始发现规律和趋势。
如上所述,您不一定需要解决用户遇到的所有问题,您需要解决(尤其是在初期)影响最多用户的问题,这应该也是对核心网页指标得分产生最大负面影响的问题。
对于 GA4,请参阅有关如何使用 BigQuery 查询和直观呈现数据的专门文章。
摘要
希望这篇博文帮助概述了如何使用现有性能 API 和 web-vitals
库获取调试信息,以便根据实际用户在现场的访问情况来诊断性能。虽然本指南侧重于核心网页指标,但其中的概念也适用于调试任何在 JavaScript 中可衡量的性能指标。
如果您刚开始衡量性能,并且已经是 Google Analytics(分析)用户,那么“网页指标报告”工具可能是不错的选择,因为它已支持报告 Core Web Vitals 指标的调试信息。
如果您是分析服务供应商,并且想要改进产品并为用户提供更多调试信息,请考虑本文介绍的一些技术,但不要局限于本文中介绍的方法。本博文面向所有分析工具,但是,各个分析工具可能(并且应该)可以(并且应该)捕获并报告更多调试信息。
最后,如果您觉得由于 API 自身的功能或信息缺失而无法调试这些指标,请将反馈发送至 web-vitals-feedback@googlegroups.com。