尋找欄位中的緩慢互動

瞭解如何找出網站欄位資料中緩慢的互動,以便找出改善 Interaction to Next Paint 的機會。

實際資料可反映實際使用者對網站的體驗。這項指標可找出單靠實驗室資料無法發現的問題。就 Interaction to Next Paint (INP) 而言,欄位資料對於找出緩慢互動至關重要,並提供重要線索,協助您修正這些互動。

本指南將說明如何使用 Chrome 使用者體驗報告 (CrUX) 的現場資料,快速評估網站的 INP,瞭解網站是否有 INP 問題。接著,您將瞭解如何使用 web-vitals JavaScript 程式庫的歸因版本,以及 長動畫影格 API (LoAF) 提供的全新洞察資料,收集及解讀網站上緩慢互動的欄位資料。

先使用 CrUX 評估網站的 INP

如果沒有從網站使用者收集現場資料,CrUX 是不錯的起點。CrUX 會從選擇傳送遙測資料的實際 Chrome 使用者收集現場資料。

CrUX 資料會顯示在多個不同區域,具體取決於您要尋找的資訊範圍。CrUX 可提供 INP 和其他 Core Web Vitals 的資料,包括:

  • 使用 PageSpeed Insights 測試個別網頁和整個來源。
  • 網頁類型。舉例來說,許多電子商務網站都有產品詳細資料頁面和產品資訊頁面類型。您可以在 Search Console 中取得不重複網頁類型的 CrUX 資料。

首先,您可以在 PageSpeed Insights 中輸入網站網址。輸入網址後,系統會顯示該網址的欄位資料 (如有),包括 INP 等多項指標。你也可以使用切換鈕,查看行動裝置和電腦維度的 INP 值。

PageSpeed Insights 中 CrUX 顯示的欄位資料,包括 LCP、INP、CLS 這三項 Core Web Vitals 指標,以及 TTFB、FCP 做為診斷指標,和 FID 做為已淘汰的 Core Web Vitals 指標。
PageSpeed Insights 顯示的 CrUX 資料讀數。在這個範例中,指定網頁的 INP 需要改善。

這項資料可協助您瞭解是否有問題。不過,CrUX 無法告訴你問題的原因。市面上有許多實際使用者監控 (RUM) 解決方案,可協助您從網站使用者收集自己的欄位資料,協助您回答這個問題。其中一個選項是使用 web-vitals JavaScript 程式庫自行收集該欄位資料。

使用 web-vitals JavaScript 程式庫收集欄位資料

web-vitals JavaScript 程式庫是一種指令碼,可在網站上載入,用於收集網站使用者的欄位資料。您可以使用這項工具記錄多項指標,包括支援 INP 的瀏覽器中的 INP。

Browser Support

  • Chrome: 96.
  • Edge: 96.
  • Firefox: 144.
  • Safari: 26.2.

Source

您可以使用 web-vitals 程式庫的標準版本,從實際使用者取得基本 INP 資料:

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  console.log(name);    // 'INP'
  console.log(value);   // 512
  console.log(rating);  // 'poor'
});

如要分析使用者的欄位資料,請將資料傳送至以下位置:

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  // Prepare JSON to be sent for collection. Note that
  // you can add anything else you'd want to collect here:
  const body = JSON.stringify({name, value, rating});

  // Use `sendBeacon` to send data to an analytics endpoint.
  // For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
  navigator.sendBeacon('/analytics', body);
});

不過,這項資料本身提供的資訊與 CrUX 差不多。這時,web-vitals 程式庫的歸因版本就能派上用場。

進一步瞭解 web-vitals 程式庫的歸因版本

web-vitals 程式庫的歸因版本會顯示可從實際使用者取得的其他資料,協助您更有效地排解影響網站 INP 的互動問題。您可以透過程式庫 onINP() 方法中顯示的 attribution 物件存取這項資料:

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

onINP(({name, value, rating, attribution}) => {
  console.log(name);         // 'INP'
  console.log(value);        // 56
  console.log(rating);       // 'good'
  console.log(attribution);  // Attribution data object
});
web-vitals 程式庫的控制台記錄顯示方式。本例中的控制台會顯示指標名稱 (INP)、INP 值 (56)、該值在 INP 門檻中的位置 (良好),以及歸因物件中顯示的各種資訊,包括長動畫影格 API 的項目。
web-vitals 程式庫中的資料在控制台中的顯示方式。

除了網頁本身的 INP 之外,歸因版本也提供大量資料,可協助您瞭解互動速度緩慢的原因,包括應著重於互動的哪個部分。這項功能可協助您解答重要問題,例如:

  • 「使用者是否在網頁載入時與網頁互動?」
  • 「互動的事件處理常式是否執行了很長一段時間?」
  • 「互動事件處理常式程式碼是否延遲啟動?如果是,當時主執行緒上還發生了什麼事?」
  • 「互動是否導致大量轉譯作業,延遲下一個影格的繪製?」

下表列出可從程式庫取得的部分基本歸因資料,協助您找出網站互動速度緩慢的一些高層級原因:

attribution 物件金鑰 資料
interactionTarget CSS 選取器,指向產生網頁 INP 值的元素,例如 button#save。
interactionType 互動類型,包括點擊、輕觸或鍵盤輸入。
inputDelay* 互動的輸入延遲。
processingDuration* 從第一個事件監聽器開始執行以回應使用者互動,到所有事件監聽器處理完畢的時間。
presentationDelay* 互動的顯示延遲,從事件處理常式完成到下一個影格繪製的時間。
longAnimationFrameEntries* 與互動相關聯的 LoAF 項目。詳情請參閱下一節。
*第 4 版新功能

從 web-vitals 程式庫第 4 版開始,您可以透過 INP 階段細目 (輸入延遲、處理時間和呈現延遲) 和 Long Animation Frames API (LoAF) 提供的資料,進一步瞭解有問題的互動。

長動畫影格 API (LoAF)

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

使用欄位資料偵錯互動是一項艱鉅的任務。不過,LoAF 會提供多項詳細時間和其他資料,可用於找出確切原因,更重要的是,找出網站程式碼中問題來源的位置,因此現在可以透過 LoAF 的資料,進一步瞭解互動速度緩慢的原因。

web-vitals 程式庫的歸因版本會在 attribution 物件的 longAnimationFrameEntries 鍵下,公開 LoAF 項目陣列。下表列出每個 LoAF 項目中可找到的幾項重要資料:

LoAF 項目物件鍵 資料
duration 長動畫影格的時間長度,直到版面配置完成為止,但不包括繪製和合成。
blockingDuration 在影格中,瀏覽器因長時間任務而無法快速回應的總時間。這段封鎖時間可能包括執行 JavaScript 的長時間任務,以及影格中任何後續的長時間算繪任務。
firstUIEventTimestamp 事件在影格期間排入佇列的時間戳記。可用於找出互動輸入延遲的開始時間。
startTime 影格的開始時間戳記。
renderStart 影格開始算繪的時間。這包括任何 requestAnimationFrame 回呼 (以及 ResizeObserver 回呼,如適用),但可能在任何樣式/版面配置工作開始之前。
styleAndLayoutStart 影格中發生樣式/版面配置作業的時間。在考量其他可用時間戳記時,可用於判斷樣式/版面配置工作的長度。
scripts 項目陣列,內含對網頁 INP 有影響的指令碼歸因資訊。
根據 LoAF 模型,長動畫影格的視覺化呈現。
根據 LoAF API (減去 blockingDuration) 顯示長動畫影格時間的圖表。

所有這些資訊都能讓你瞭解互動速度緩慢的原因,但 LoAF 項目顯示的 scripts 陣列應特別值得注意:

指令碼歸因物件鍵 資料
invoker 叫用者。這會因下一列所述的叫用者類型而異。叫用者範例包括 'IMG#id.onload'、'Window.requestAnimationFrame' 或 'Response.json.then' 等值。
invokerType 叫用者類型。可以是 'user-callback'、'event-listener'、'resolve-promise'、'reject-promise'、'classic-script' 或 'module-script'。
sourceURL 長動畫影格來源指令碼的網址。
sourceCharPosition sourceURL 識別的指令碼中的字元位置。
sourceFunctionName 所識別指令碼中的函式名稱。

這個陣列中的每個項目都包含下表所示的資料,可提供造成互動緩慢的指令碼相關資訊,以及造成互動緩慢的原因。

評估並找出互動速度緩慢的常見原因

為了讓您瞭解如何使用這項資訊,本指南將逐步說明如何使用 web-vitals 程式庫中顯示的 LoAF 資料,判斷互動速度緩慢的原因。

處理時間過長

互動的處理時間長度,是指互動的已註冊事件處理常式回呼執行到完成所需的時間,以及兩者之間可能發生的任何其他事件。web-vitals 程式庫會顯示處理時間過長的情況:

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

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5
});

一般人通常會認為,互動速度緩慢的主要原因是事件處理常式程式碼執行時間過長,但實際情況不一定如此!確認這是問題所在後,你可以使用 LoAF 資料進一步探究:

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

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5

  // Get the longest script from LoAF covering `processingDuration`:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Get attribution for the long-running event handler:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

如上述程式碼片段所示,您可以運用 LoAF 資料,找出互動處理時間過長的確切原因,包括:

  • 元素和已註冊的事件監聽器。
  • 包含長時間執行事件處理常式程式碼的指令碼檔案,以及該檔案中的字元位置。
  • 函式名稱。

這類資料非常寶貴。您不必再費心找出究竟是哪項互動 (或其事件處理常式) 導致處理時間過長。此外,由於第三方程式碼通常可以註冊自己的事件處理常式,因此您可以判斷是否是您的程式碼造成問題!對於可控制的程式碼,建議您最佳化長時間任務。

輸入延遲時間過長

雖然長時間執行的事件處理常式很常見,但互動還有其他部分需要考量。其中一部分發生在處理時間長度之前,稱為「輸入延遲」。這是指從使用者發起互動,到事件處理常式回呼開始執行的時間,這段時間內主執行緒已在處理其他工作。web-vitals 程式庫的歸因版本可提供互動的輸入延遲時間長度:

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

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536
});

如果發現某些互動的輸入延遲時間偏長,就必須找出互動發生時,網頁上發生了什麼事,導致輸入延遲時間偏長。這通常取決於互動是在網頁載入時發生,還是之後發生。

是否在載入網頁時發生?

載入網頁時,主執行緒通常最忙碌。在這段期間,系統會將各種工作排入佇列並進行處理,如果使用者在所有工作進行時嘗試與網頁互動,可能會延遲互動。載入大量 JavaScript 的網頁可能會開始編譯及評估指令碼,並執行函式,讓網頁準備好與使用者互動。如果使用者在活動發生時進行互動,這項工作可能會造成干擾,您可以查看網站使用者是否遇到這種情況:

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

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Invoker types can describe if script eval blocked the main thread:
    const {invokerType} = script;    // 'classic-script' | 'module-script'
    const {sourceLocation} = script; // 'https://example.com/app.js'
  }
});

如果您在現場記錄這項資料,並發現輸入延遲時間偏高,且叫用器類型為 'classic-script' 或 'module-script',則可以合理推斷網站上的指令碼需要很長時間才能評估,且會阻斷主執行緒,導致互動延遲。如要縮短這段封鎖時間,您可以將指令碼拆成較小的套件、延後載入一開始未使用的程式碼,以及稽核網站,找出可完全移除的未使用程式碼。

是在網頁載入後嗎?

雖然輸入延遲通常發生在網頁載入期間,但也有可能在網頁載入後發生,原因完全不同。載入網頁後輸入延遲的常見原因,可能是先前 setInterval 呼叫導致程式碼定期執行,甚至是排定在稍早執行且仍在處理中的事件回呼。

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

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    const {invokerType} = script;        // 'user-callback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

與排解高處理時間值的問題一樣,如果輸入延遲時間過長的原因如前所述,您會取得詳細的指令碼歸因資料。不過,叫用器類型會根據延遲互動的工作性質而有所不同:

  • 'user-callback' 表示封鎖工作來自 setInterval、setTimeout,甚至是 requestAnimationFrame。
  • 'event-listener' 表示阻礙工作來自先前輸入的內容,該內容已排入佇列,但仍在處理中。
  • 'resolve-promise' 和 'reject-promise' 表示封鎖工作來自先前啟動的非同步工作,並在使用者嘗試與網頁互動時解決或拒絕,導致互動延遲。

無論如何,指令碼歸因資料都能讓你瞭解從何處著手,以及輸入延遲是否是由自己的程式碼或第三方指令碼造成。

回應顯示延遲時間過長

呈現延遲是互動的最後一哩路,從互動的事件處理常式完成開始,直到下一個影格繪製完成為止。當事件處理常式中的工作因互動而變更使用者介面的視覺狀態時,就會發生這種情況。與處理時間和輸入延遲一樣,web-vitals 程式庫可以告訴你互動的呈現延遲時間:

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

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691
});

如果您記錄這項資料,並發現網站 INP 的互動呈現延遲時間過長,原因可能不盡相同,但以下是幾個需要留意的可能原因。

耗費資源的樣式和版面配置作業

呈現延遲時間過長,可能是因為重新計算樣式和版面配置作業耗費大量資源,而這可能是由多種原因造成,包括複雜的 CSS 選取器和大型 DOM。您可以使用 web-vitals 程式庫中顯示的 LoAF 時間,測量這項工作的持續時間:

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

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  // Get necessary timings:
  const {startTime} = loaf; // 2120.5
  const {duration} = loaf;  // 1002

  // Figure out the ending timestamp of the frame (approximate):
  const endTime = startTime + duration; // 3122.5

  // Get the start timestamp of the frame's style/layout work:
  const {styleAndLayoutStart} = loaf; // 3011.17692309

  // Calculate the total style/layout duration:
  const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running style and layout operation:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

LoAF 不會顯示影格的樣式和版面配置作業時間長度,但會顯示開始時間。有了這個起始時間戳記,您就可以使用 LoAF 的其他資料,判斷影格的結束時間,並從中減去樣式和版面配置工作的起始時間戳記,藉此計算該工作的準確持續時間。

長時間執行的 requestAnimationFrame 回呼

requestAnimationFrame 回呼中執行的工作過多,可能是導致顯示延遲時間過長的原因之一。這個回呼的內容會在事件處理常式執行完畢後執行,但會在樣式重新計算和版面配置工作之前執行。

如果回呼中執行的工作很複雜,可能需要相當長的時間才能完成。如果您懷疑顯示延遲值偏高是因為使用 requestAnimationFrame 進行的工作,可以使用 web-vitals 程式庫提供的 LoAF 資料,找出下列情況:

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 543.1999999880791

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  // Get the render start time and when style and layout began:
  const {renderStart} = loaf;         // 2489
  const {styleAndLayoutStart} = loaf; // 2989.5999999940395

  // Calculate the `requestAnimationFrame` callback's duration:
  const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running requestAnimationFrame callback:
    const {invokerType} = script;        // 'user-callback'
    const {invoker} = script;            // 'FrameRequestCallback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

如果您發現顯示延遲時間有很大一部分花在 requestAnimationFrame 回呼,請確保您在這些回呼中執行的工作僅限於會實際更新使用者介面的工作。任何其他不涉及 DOM 或更新樣式的工作,都會不必要地延遲下一個影格的繪製,因此請務必小心!

結論

如要瞭解實際使用者在互動時遇到哪些問題,欄位資料是最佳資訊來源。只要使用 web-vitals JavaScript 程式庫 (或 RUM 供應商) 等欄位資料收集工具,就能更準確地判斷哪些互動最容易發生問題,然後在實驗室中重現問題互動,再著手修正。