瞭解如何在網站的欄位資料中找出緩慢互動,藉此找出改善「與下一個顯示畫面互動」的契機。
「欄位資料」是一種資料,有助您瞭解使用者實際瀏覽網站的情形。這能歸納出在研究室資料中找不到的問題。就與下一個顯示的內容互動 (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 值。
這項資料可以判斷您是否發生問題,然而,CrUX 不會告訴您,問題是出在「什麼」。目前有許多即時使用者監控 (RUM) 解決方案可協助您向網站使用者收集自己的欄位資料以回答這個問題,您也可以選擇使用網頁中的 JavaScript 程式庫自行收集欄位資料。
使用 web-vitals
JavaScript 程式庫收集欄位資料
您可以在網站中載入 web-vitals
JavaScript 程式庫這個指令碼,收集網站使用者的欄位資料。您可以用這份報表記錄多項指標,包括在支援 INP 的瀏覽器中記錄資料。
網路 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 更多。這時,網路 Vitals 資料庫的歸因版本就能派上用場。
使用網頁 Vitals 程式庫的歸因建構功能,進一步提升成效
網站 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 本身外,歸因版本也提供許多資料,可用於瞭解互動速度緩慢的原因,包括您應著重在互動的哪些部分。這項功能可協助您解答重要問題,例如:
- 「使用者在網頁載入期間是否與網頁互動?」
- 「互動的事件處理常式是否長時間執行?」
- 「互動事件處理常式程式碼是否延遲啟動?如果有,目前主執行緒上還有其他問題嗎?」
- 「這次互動會造成許多轉譯工作延遲,導致下一個影格繪製延遲嗎?」
下表列出可從資料庫取得的基本歸因資料,有助您找出導致網站互動速度緩慢的常見原因:
attribution 物件金鑰
|
資料 |
---|---|
interactionTarget
|
CSS 選取器,指向產生網頁 INP 值的元素,例如 button#save 。
|
interactionType
|
互動類型,來自點擊、輕觸或鍵盤輸入。 |
inputDelay *
|
互動的輸入延遲時間。 |
processingDuration *
|
從第一個事件監聽器開始執行,以回應使用者互動而花費的時間,直到所有事件監聽器處理完畢為止。 |
presentationDelay *
|
互動的「顯示延遲」,從事件處理常式完成到下一個影格的繪製時間開始。 |
longAnimationFrameEntries *
|
與互動相關 LoAF 中的項目。詳情請參閱下文。 |
從 Web-Vitals 程式庫第 4 版開始,就能透過 INP 階段細目 (輸入延遲、處理時間長度與顯示延遲時間) 和 Long Animation Frames API (LoAF) 等資料,深入分析有問題的互動。
長動畫頁框 API (LoAF)
使用欄位資料偵錯互動並不容易。不過,由於 LoAF 提供資料,因此你現在還能深入分析互動速度緩慢的原因,因為 LoAF 提供許多詳細時間和其他資料,方便你精準找出確切原因。更重要的是,問題來源是你網站程式碼中的問題來源。
網路 Vitals 程式庫的歸因版本會在 attribution
物件的 longAnimationFrameEntries
鍵下方,公開一系列 LoAF 項目。下表列出各 LoAF 項目中的幾項重要資料:
LoAF 項目物件金鑰 | 資料 |
---|---|
duration
|
長動畫影格的時間長度,到版面配置結束為止,但不包括繪製和合成。 |
blockingDuration
|
在影格中,瀏覽器因長時間工作而無法快速回應的總時間長度。這類封鎖時間可能包含執行 JavaScript 的長時間工作,以及影格中任何後續的長時間轉譯工作。 |
firstUIEventTimestamp
|
在影格中,事件排入佇列的時間戳記。有助於瞭解互動的「輸入延遲」開始時間。 |
startTime
|
影格的開始時間戳記。 |
renderStart
|
影格轉譯工作開始的時間。這包括所有 requestAnimationFrame 回呼 (以及 ResizeObserver 回呼 (如適用),但可能會早於任何樣式/版面配置工作開始)。
|
styleAndLayoutStart
|
在影格中運作樣式/版面配置時。使用其他可用時間戳記時,這有助於瞭解樣式/版面配置工作的長度。 |
scripts
|
一組項目陣列,包含產生網頁 INP 的指令碼屬性資訊。 |
以上資訊都能充分說明互動緩慢的原因,但 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 資料,協助您瞭解這些資訊的可能用途。
處理時間過長
互動的處理時間是指互動的已註冊事件處理常式回呼從執行到完成所需要的時間,以及兩者之間可能發生的其他情況。網頁 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 資料,追蹤處理時長值互動背後的確切原因,包括:
- 元素及其註冊的事件監聽器。
- 包含長時間執行事件處理常式程式碼的指令碼檔案—及其其中的字元位置。
- 函式的名稱。
這類資料非常寶貴。您不必再一件複雜的事情,找出哪些互動 (或其中的事件處理常式) 需要的處理時間較長。此外,由於第三方指令碼通常可以註冊自己的事件處理常式,因此您可以判斷它是否由負責的程式碼負責。對於您可以掌控的程式碼,建議您瞭解如何最佳化長時間工作。
輸入時間過長
雖然長時間執行的事件處理常式很常見,但還有其他部分需要考量。也就是「輸入延遲」處理時間之前的部分發生。這是指從使用者啟動互動開始,到事件處理常式回呼開始執行的時間,以及主執行緒已經處理另一項工作的時間。網路 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'
表示造成阻斷工作是來自setInterval
、setTimeout
,甚至是requestAnimationFrame
。'event-listener'
表示封鎖工作是來自已排入佇列且仍在處理的先前輸入項目。'resolve-promise'
和'reject-promise'
表示封鎖工作來自先前啟動的一些非同步工作,並在使用者嘗試與網頁互動時予以解決或拒絕,從而延誤互動。
在任何情況下,您都可以透過指令碼歸因資料判斷該從何處著手,以及輸入延遲是出自自己的程式碼,還是第三方指令碼所致。
顯示時間過長
顯示延遲是互動的最後一英里,從互動的事件處理常式完成時開始,到繪製下一個影格的時間點為止。當互動導致使用者介面的視覺狀態改變,導致事件處理常式中的工作發生時,就會發生這類錯誤。如同處理時間長度和輸入延遲,網站-Vitals 程式庫可顯示互動的顯示延遲時間長度:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
如果在記錄這些資料後發現影響網站 INP 的互動顯示很長一段時間,問題可能出在差不多,但以下列舉幾項原因。
昂貴的樣式和版面配置
顯示時間較長的樣式重新計算和版面配置工作,可能是由許多原因造成,包括複雜的 CSS 選取器和大型 DOM 大小。您可以使用網頁開發資料庫中顯示的 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
運作的原因是工作造成的延遲顯示時間過長,可以使用網頁 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。