尋找欄位中的緩慢互動

瞭解如何在網站的欄位資料中找出互動速度緩慢的情況,找出改善「與下一個顯示的內容互動」指標的機會。

實地資料是指實際使用者對網站的體驗。這項工具可找出您無法透過實驗室資料找出的問題。在Interaction to Next Paint (INP) 方面,實地資料對於找出互動速度緩慢的問題至關重要,而且還能提供重要線索,協助您解決問題。

本指南將說明如何使用 Chrome 使用者體驗報告 (CrUX) 的欄位資料,快速評估網站的 INP 情形,瞭解網站是否有 INP 問題。接著,您將瞭解如何使用 web-vitals JavaScript 程式庫的歸因版本,以及該版本從 Long Animation Frames 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 顯示的欄位資料,顯示三項 Core Web Vitals 的 LCP、INP 和 CLS,以及 TTFB 和 FCP 診斷指標,以及已淘汰的 Core Web Vitals 指標 FID。
PageSpeed Insights 中的 CrUX 資料讀數。在這個範例中,該網頁的 INP 需要改善。

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

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

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

瀏覽器支援

  • Chrome:96。
  • Edge:96。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

您可以使用 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 門檻內 (良好)),以及歸因物件中顯示的各種資訊位元,包括 Long Animation Frames 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)

瀏覽器支援

  • Chrome:123。
  • Edge:123。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

使用欄位資料對互動進行偵錯是一項艱鉅的任務。不過,有了 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.sort((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.sort((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.sort((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' 表示封鎖工作來自 setIntervalsetTimeout,甚至 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.sort((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.sort((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 供應器) 等實地資料收集工具,更有信心地找出哪些互動最有問題,然後在實驗室中重現有問題的互動,並著手修正。

主頁橫幅圖片取自 Unsplash,由 Federico Respini 提供。