尋找欄位中的緩慢互動

瞭解如何找出網站實際工作環境資料中的緩慢互動,以便找出改善「與下一個顯示的內容互動」指標的機會。

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

本指南將說明如何使用 Chrome 使用者體驗報告 (CrUX) 的欄位資料,快速評估網站的 INP,判斷網站是否有 INP 問題。接著,您將瞭解如何使用網頁指標 JavaScript 程式庫的歸因建構版本,以及長時間動畫影格 API (LoAF) 提供的新洞察資料,收集及解讀網站上互動緩慢的實際工作環境資料。

請先使用 CrUX 評估網站的 INP

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

系統會在多個不同區域顯示 CrUX 資料,具體取決於您要尋找的資訊範圍。CrUX 可提供 INP 和其他網站使用體驗核心指標的資料,適用於:

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

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

PageSpeed Insights 中的 CrUX 顯示現場資料,包括三項網站體驗核心指標 (LCP、INP、CLS)、診斷指標 (TTFB、FCP),以及已淘汰的網站體驗核心指標 (FID)。
PageSpeed Insights 中顯示的 CrUX 資料讀取值。在這個範例中,指定網頁的 INP 需要改善。

這項資料很有用,因為可判斷是否有問題。不過,CrUX 無法告訴您哪些因素導致問題。市面上有許多實際使用者監控 (RUM) 解決方案,可協助您從網站使用者收集自己的現場資料,進而回答上述問題。其中一個選項是使用 web-vitals JavaScript 程式庫自行收集現場資料。

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

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

Browser Support

  • Chrome: 96.
  • Edge: 96.
  • Firefox Technology Preview: supported.
  • Safari: not supported.

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 程式庫的歸因建構功能,進一步瞭解成效

網頁指標程式庫的歸因建構會顯示可從現場使用者取得的其他資料,協助您更妥善地排解影響網站 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
});
網頁核心指標程式庫的控制台記錄顯示方式。這個範例中的控制台會顯示指標名稱 (INP)、INP 值 (56)、該值在 INP 門檻中的位置 (良好),以及歸因物件中顯示的各種資訊,包括來自 Long Animation Frames API 的項目。
網頁指標程式庫的資料在控制台中顯示的方式。

除了網頁的 INP 本身,歸因建構作業還會提供許多資料,協助您瞭解互動緩慢的原因,包括應著重於互動的哪個部分。這項功能可協助您解答重要問題,例如:

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

下表列出可從程式庫取得的部分基本歸因資料,有助於找出網站上互動緩慢的一些高階原因:

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

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

Long Animation Frames API (LoAF)

Browser Support

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

Source

使用欄位資料偵錯互動並不容易。不過,有了 LoAF 的資料,您現在可以更深入瞭解互動緩慢的原因,因為 LoAF 會公開許多詳細的時間資訊和其他資料,協助您找出確切原因,更重要的是,找出網站程式碼中的問題來源。

網頁指標程式庫的歸因建構版本會在 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' 表示封鎖工作來自 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.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 或更新樣式的工作,都會不必要地延遲繪製下一個影格,因此請務必小心!

結論

現場資料是瞭解現場實際使用者遇到哪些互動問題的最佳資訊來源。運用網頁指標 JavaScript 程式庫 (或 RUM 供應商) 等現場資料收集工具,您就能更確信哪些互動最容易發生問題,然後在實驗室中重現問題互動,並著手修正。

主頁橫幅圖片來自 Unsplash,由 Federico Respini 拍攝。