尋找欄位中的緩慢互動

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

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

本指南將說明如何使用 Chrome 使用者體驗報告 (CrUX) 中的欄位資料,確認網站是否有 INP 相關問題,快速評估網站的 INP。接下來,您將瞭解如何使用網頁 Vitals JavaScript 程式庫的歸因版本,以及 Long Animation Frames API (LoAF) 提供的新深入分析,收集並解讀網站上互動速度緩慢的欄位資料。

請先使用 CrUX 評估網站的 INP

如果您尚未收集網站使用者的現場資料,CrUX 可能是一個不錯的起點。如果 Chrome 使用者選擇傳送遙測資料,CrUX 就會向這些使用者收集現場資料。

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 程式庫的歸因建構功能就能派上用場。

使用網頁 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 Frame 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 供應器) 等實地資料收集工具,更有信心地找出哪些互動最有問題,然後在實驗室中重現有問題的互動,並著手修正。

主頁橫幅由 Federico Respini 提供。Unsplash 提供。