Long Animation Frames API (LoAF 發音 Lo-Af) 是 Long Tasks API 的更新版本,可讓您進一步瞭解使用者介面 (UI) 更新速度緩慢。如要找出可能影響「與下一個顯示動作互動 (INP)」 Core Web Vitals 指標的緩慢動畫影格,這有助於找出其他會影響流暢度的 UI 資源浪費畫面。
API 狀態
繼從 Chrome 116 至 Chrome 122 的來源試用結束後,LoAF API 已經從 Chrome 123 推出。
背景:Long Tasks API
Long Animation Frames API 是 Chrome 目前推出已有一段時間 (自 Chrome 58 版) 提供的 Long Tasks API 的替代選項。顧名思義,Long Task API 可讓您監控長時間工作,這些工作會在主執行緒中佔用 50 毫秒或更久。您可以使用 PerformanceLongTaskTiming
介面,透過 PeformanceObserver
監控長時間工作:
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries());
});
observer.observe({ type: 'longtask', buffered: true });
長時間工作可能會造成回應速度問題。如果使用者嘗試與網頁互動 (例如點選按鈕或開啟選單),但主執行緒已在處理較長的工作,則使用者的互動會延遲,等候工作完成。
為改善回應速度,我們一般建議拆分長時間的工作。如果將每個長時間工作拆成一系列較小型的工作,這個工作可能會允許更多重要的工作在彼此間執行,以免回應互動時發生嚴重的延遲。
因此,想要提升回應速度時,首先通常會執行效能追蹤並查看長時間工作。例如使用 Lighthouse 等研究室式稽核工具 (避免長時間的主執行緒工作),或是在 Chrome 開發人員工具中查看長時間工作。
進行實驗室導向測試時,通常無法取得反應問題,因為這些工具可能不會包含互動情形,但這類測試僅是一小部分可能發生的互動。在理想情況下,建議您在領域中評估緩慢互動的原因。
Long Tasks API 的缺點
利用 Performance Observer 評估實地工作的長時間工作,只有些許實用。實際上,除了長時間工作完成以及花費了多久後,我們還無法提供更多資訊。
即時使用者監控 (RUM) 工具通常會利用這項資訊來呈現長時間工作的數量或持續時間,或是找出這些作業發生的網頁,但如果沒有有關長時間工作的原因詳細資料,這類工具僅會受到限制。Long Tasks API 只提供「基本歸因模式」,這個模型最好只告訴您容器長時間工作發生在 (頂層文件或 <iframe>
),但不會提供呼叫該模型的指令碼或函式,如一般項目所示:
{
"name": "unknown",
"entryType": "longtask",
"startTime": 31.799999997019768,
"duration": 136,
"attribution": [
{
"name": "unknown",
"entryType": "taskattribution",
"startTime": 0,
"duration": 0,
"containerType": "window",
"containerSrc": "",
"containerId": "",
"containerName": ""
}
]
}
Long Tasks API 也可能排除一些重要工作,因此也是不完整的檢視畫面。部分更新 (例如轉譯) 會同時處理在理想情況下應一併處理的獨立工作,導致更新時能準確評估「工作總數」之後的 12 點。如要進一步瞭解依賴工作的限制,請參閱「長時間工作不敷使用」一節。
最後一個問題是,測量長時間工作只會回報時間超過 50 毫秒限制的個別工作。動畫影格可能是由小於 50 毫秒限制的多項工作組成,但同時仍會阻斷瀏覽器的轉譯功能。
Long Animation Frame API
Long Animation Frames API (LoAF) 是全新的 API,旨在解決 Long Tasks API 的缺點,讓開發人員取得更多可做為行動依據的洞察資料,協助解決回應問題並改善 INP。
良好的回應式是指網頁回應與網頁互動的速度。也就是能及時繪製使用者所需的任何更新,並避免阻礙更新作業。針對 INP,建議在 200 毫秒內回應,但對於其他更新 (例如動畫) 來說,回應時間可能太長。
Long Animation Frames API 是另一種評估封鎖工作的方法。Long Animation Frames API 不是測量個別「工作」,而是以其名稱建議來評估長動畫影格。長動畫影格是指轉譯更新延遲超過 50 毫秒 (與 Long Tasks API 的門檻相同)。
長動畫影格的觀測方式與使用 PerformanceObserver
的工作類似,但改為查看 long-animation-frame
類型:
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries());
});
observer.observe({ type: 'long-animation-frame', buffered: true });
const loafs = performance.getEntriesByType('long-animation-frame');
不過,效能項目使用 maxBufferSize
,之後會捨棄較新的項目,因此建議使用 PerformanceObserver。long-animation-frame
緩衝區空間設為 200,與 long-tasks
相同。
參考框架 (而非工作) 的優點
從影格視角 (而非工作模式) 查看這點的主要優點是,較長的動畫可由會觸發較長動畫影格的任意數量任務組成。這解決了前文提到的最後一個點,Long Tasks API 可能無法顯示動畫影格之前,許多較小型且會妨礙算繪的工作總和。
進行長時間工作的這項替代檢視畫面,是提供整個影格時間詳細分析資料的功能。LoAF 不只是加入 startTime
和 duration
(例如 Long Tasks API),還會針對影格持續時間的各個部分提供更詳細的資訊,包括:
startTime
:相對於導覽開始時間的長動畫影格開始時間。duration
:長動畫影格的時間長度 (不含顯示時間)。renderStart
:轉譯週期的開始時間,包括requestAnimationFrame
回呼、樣式和版面配置計算、大小調整觀察器和交集觀察器回呼。styleAndLayoutStart
:計算樣式和版面配置時,所花費時間範圍的開始時間。firstUIEventTimestamp
:在這個影格中,要在這個影格處理的第一個 UI 事件 (滑鼠/鍵盤等) 的時間。blockingDuration
:封鎖動畫影格的時間長度 (以毫秒為單位)。
這些時間戳記可讓長動畫影格劃分為時間:
時間 | 計算方式 |
---|---|
開始時間 | startTime |
結束時間 | startTime + duration |
工作時間長度 | renderStart ? renderStart - startTime : duration |
轉譯作業時間長度 | renderStart ? (startTime + duration) - renderStart: 0 |
算繪:預先版面配置時間長度 | styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0 |
算繪:樣式和版面配置持續時間 | styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0 |
如要進一步瞭解這些個別顯示時間,請參閱說明,其中詳細介紹了導致動畫影格產生播放時間的活動。
改善歸因分析
long-animation-frame
項目類型可針對每個促成較長動畫影格的指令碼,提供更準確的歸因資料。
與 Long Tasks API 類似,我們會以歸因項目陣列提供這項資訊,其中包含以下各項詳細資料:
name
和EntryType
都會傳回script
。- 有意義的
invoker
,指出如何呼叫指令碼 (例如'IMG#id.onload'
、'Window.requestAnimationFrame'
或'Response.json.then'
)。 - 指令碼進入點的
invokerType
:user-callback
:從網路平台 API (例如setTimeout
、requestAnimationFrame
) 註冊的已知回呼。event-listener
:平台事件 (例如click
、load
、keyup
) 的事件監聽器。resolve-promise
:平台承諾的處理常式 (例如fetch()
。請注意,在承諾中,所有相同承諾的處理常式會合併為一個「指令碼」).
reject-promise
:根據resolve-promise
,但拒絕。classic-script
:指令碼評估 (例如<script>
或import()
)module-script
:與classic-script
相同,但適用於模組指令碼。
- 將該指令碼的時間資料分開:
startTime
:叫用項目函式的時間。duration
:從startTime
到後續微工作佇列完成處理的時間。executionStart
:編譯後的時間。forcedStyleAndLayoutDuration
:在這個函式中處理強製版面配置和樣式所花費的總時間 (請參閱「輾轉現象」)。pauseDuration
:「暫停」狀態的總時間同步作業 (警示、同步 XHR)。
- 指令碼來源詳細資料:
sourceURL
:可用的指令碼資源名稱 (如果找不到,則為空白)。sourceFunctionName
:指令碼函式名稱 (如有),或留空。sourceCharPosition
:如有可用的指令碼字元位置,或者找不到,則為 -1。
windowAttribution
:播放長動畫影格的容器 (頂層文件或<iframe>
)。window
:對相同來源視窗的參照。
如果提供來源項目,開發人員就能確切掌握長動畫影格中每個指令碼的呼叫方式,精細程度可達呼叫指令碼中的字元位置。這可以取得導致動畫影格所產生 JavaScript 資源中的確切位置。
long-animation-frame
成效項目範例
含有單一指令碼的完整 long-animation-frame
效能輸入範例如下:
{
"blockingDuration": 0,
"duration": 60,
"entryType": "long-animation-frame",
"firstUIEventTimestamp": 11801.099999999627,
"name": "long-animation-frame",
"renderStart": 11858.800000000745,
"scripts": [
{
"duration": 45,
"entryType": "script",
"executionStart": 11803.199999999255,
"forcedStyleAndLayoutDuration": 0,
"invoker": "DOMWindow.onclick",
"invokerType": "event-listener",
"name": "script",
"pauseDuration": 0,
"sourceURL": "https://web.dev/js/index-ffde4443.js",
"sourceFunctionName": "myClickHandler",
"sourceCharPosition": 17796,
"startTime": 11803.199999999255,
"window": [Window object],
"windowAttribution": "self"
}
],
"startTime": 11802.400000000373,
"styleAndLayoutStart": 11858.800000000745
}
如您所見,這提供了前所未有的資料量,幫助網站瞭解造成轉譯更新延遲的原因。
在欄位中使用 Long Animation Frame API
Chrome 開發人員工具和 Lighthouse 等工具雖然有助於發現和重現問題,但這些都是研究室工具,因此可能會錯過只有現場資料才能提供的使用者體驗的重要層面。
Long Animation Frames API 的用途是在欄位中,針對 Long Tasks API 無法進行的使用者互動收集重要的內容比對資料。這有助於找出您之前可能無法發現的互動,並重現問題。
功能偵測長動畫影格 API 支援
你可以使用以下程式碼測試系統是否支援 API:
if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
// Monitor LoAFs
}
連結至最長的 INP 互動
Long Animation Frames API 最顯而易見的使用案例,就是協助診斷及修正「與下一個繪製問題互動 (INP)」問題,這也是 Chrome 團隊開發這個 API 的主要原因之一。良好的 INP 是指所有互動的回應在 200 毫秒內 (從互動開始到繪製畫面為止) 的 200 毫秒內。此外,由於 Long Animation Frames API 會評估所有耗時 50 毫秒以上的影格,因此大部分有問題的 INP 都應納入 LoAF 資料,協助您診斷這類互動。
INP LoAF是包含 INP 互動的 LoAF,如下圖所示:
在某些情況下,INP 事件可能會跨越兩個 LoAF (通常是在影格開始轉譯部分之後才發生互動),因此在下一個影格中處理的事件處理常式:
在極少數的情況下,它甚至可能跨越兩個 LoAF。
記錄與 INP 互動相關的 LoAF 資料,可讓您進一步瞭解 INP 互動並進行診斷。這特別有助於瞭解輸入延遲,因為您可以查看該影格中執行的其他指令碼。
如果您未說明的處理時間和顯示延遲問題,還有未說明的處理時間和顯示延遲情況會有幫助。
沒有直接 API 將 INP 項目連結至相關的 LoAF 項目或項目,但是您可以在程式碼中比較每個項目的開始和結束時間 (請參閱 WhyNp 範例指令碼)。
web-vitals
程式庫包含與第 4 版 INP 歸因介面 longAnimationFramesEntries
屬性中交集的所有 LoAF。
連結 LoAF 項目或項目後,即可透過 INP 歸因加入資訊。scripts
物件包含一些最有價值的資訊,因為它可顯示這些頁框中的其他執行程式運作,因此向分析服務向 Google 表明瞭這些資料,可讓您進一步瞭解互動速度緩慢的原因。
回報 INP 互動的 LoAF 是個不錯的方法,可以找出網頁上最迫切的互動問題。每位使用者都可能以不同方式與您網頁互動,並獲得足夠的 INP 歸因資料,INP 歸因資料會包含一些潛在問題。這樣就能按數量排序指令碼,瞭解哪些指令碼與緩慢的 INP 相關聯。
將更多長的動畫資料回報給數據分析端點
如果只針對 INP LoAF 查看,您有可能會錯過其他有潛力的改進空間,避免日後發生 INP 問題。結果反而有點追趕您修正 INP 問題,預期會有很大的改進,但光是發生時間最慢的互動就只有少許優於,因此 INP 無法大幅改善。
因此,與其只單純著眼於 INP LoAF,而是要考慮整個網頁效期中的所有 LoAF:
不過,每個 LoAF 項目都包含可觀的資料,因此你可能不會想信滿全部。您可以將分析範圍限制在部分 LoAF 或部分資料。
以下列舉幾個建議的模式:
請根據最佳化歷程的進度,以及動畫影格的常用時長,判斷以下哪一種模式最適合您。如果網站從未針對回應速度進行最佳化處理,您可能會希望將測試範圍限制在包含互動的 LoAF,或是設定較高的門檻,或者只查看最差的網站。解決常見的回應問題時,您可以擴大評估範圍,包括不限制互動,以及降低門檻,也可以尋找特定模式。
觀察含互動的長動畫影格
如要取得 INP 長動畫影格以外的深入分析資訊,可以查看所有互動的 LoAF 互動 (可透過出現 firstUIEventTimestamp
值偵測)。
這種做法也比單純監控 INP LoAF 更加簡單,後者可能更為複雜。在大部分的情況下,這項資訊會包含特定造訪的 INP LoAF,但在極少數的情況下,若長時間互動仍沒有需要修正,因為可能是其他使用者的 INP 互動。
下列程式碼會記錄影格期間發生互動發生,且大於 150 毫秒的所有 LoAF 項目。這裡選擇 150,因為它略低於 200 毫秒的「良好」INP 門檻。您可以視需要選擇較高或較低的值。
const REPORTING_THRESHOLD_MS = 150;
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.duration > REPORTING_THRESHOLD_MS &&
entry.firstUIEventTimestamp > 0
) {
// Example here logs to console, but could also report back to analytics
console.log(entry);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
觀察超過特定門檻的動畫影格
另一種策略是監控所有 LoAF,並將超過特定門檻的信標指向分析端點,以便日後分析:
const REPORTING_THRESHOLD_MS = 150;
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.duration > REPORTING_THRESHOLD_MS) {
// Example here logs to console, but could also report back to analytics
console.log(entry);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
由於長動畫影格項目可能非常龐大,開發人員應決定要將該項目中的哪些資料傳送至數據分析。例如,項目的摘要時間,或是指令碼名稱或其他可能被視為必要其他比對內容資料的最低條件組合。
觀察最糟的長動畫影格
網站可能希望在動畫影格 (或影格) 最長的情況下收集資料,藉此減少需要信標的資料量,而不是設定門檻。因此,無論網頁有多長的動畫影格數,只有最糟的一、五或多長的動畫影格才沒有必然被反射。
MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];
const observer = new PerformanceObserver(list => {
longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
(a, b) => b.blockingDuration - a.blockingDuration
).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });
您也可以將這些策略搭配使用,只觀察 10 段最差的 LoAF,且互動時間超過 150 毫秒。
適時 (最好使用 visibilitychange
事件) 信標返回數據分析。進行本機測試時,您可以定期使用 console.table
:
console.table(longestBlockingLoAFs);
找出長動畫影格的常見模式
另一種做法是查看長篇動畫影格項目中最常出現的指令碼。資料可在指令碼和角色位置層級回報,用於識別累犯。
如果自訂平台因主題或外掛程式而造成效能問題,容易在數個網站上發現,這個做法特別有效。
長動畫影格中的常見指令碼 (或第三方來源) 執行時間可以加總和回報,以找出在網站或一組網站上長動畫影格的常見貢獻者。舉例來說,請查看網址:
const observer = new PerformanceObserver(list => {
const allScripts = list.getEntries().flatMap(entry => entry.scripts);
const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
allScripts.filter(script => script.sourceURL === sourceURL)
]));
const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
sourceURL,
count: scripts.length,
totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
}));
processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
// Example here logs to console, but could also report back to analytics
console.table(processedScripts);
});
observer.observe({type: 'long-animation-frame', buffered: true});
輸出結果範例如下:
(index) |
sourceURL |
count |
totalDuration |
---|---|---|---|
0 |
'https://example.consent.com/consent.js' |
1 |
840 |
1 |
'https://example.com/js/analytics.js' |
7 |
628 |
2 |
'https://example.chatapp.com/web-chat.js' |
1 |
5 |
在工具中使用 Long Animation Frame API
這個 API 也允許其他開發人員工具進行本機偵錯。雖然 Lighthouse 和 Chrome 開發人員工具等工具也能使用較低層級的追蹤記錄詳細資料來收集大部分的這類資料,但有了這個高階 API,其他工具或許就能存取這些資料。
在開發人員工具中顯示長動畫影格資料
您可以利用 performance.measure()
API,在開發人員工具中顯示長動畫影格,這個 API 會顯示在開發人員工具的使用者時間軌中,指出應著重改善效能的重點:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
performance.measure('LoAF', {
start: entry.startTime,
end: entry.startTime + entry.duration,
});
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
長期來看,這個程式庫可能會併入開發人員工具本身,但從先前的程式碼片段改為顯示該程式碼。
在其他開發人員工具中使用長動畫影格資料
網站體驗指標擴充功能會在記錄摘要偵錯資訊中顯示值,方便您診斷效能問題。
現在還可顯示每個 INP 回呼和每次互動的長動畫影格資料:
在自動測試工具中使用長動畫影格資料
同樣地,CI/CD 管道中的自動化測試工具可在執行各種測試套件時,評估較長的動畫影格,藉此找出潛在效能問題的詳細資料。
常見問題
有關這個 API 的常見問題包括:
為何不只要擴充或疊代 Long Tasks API?
這是另一種做法是查看潛在回應問題的評估方式,但兩者相似但最終不同。請務必確保使用現有 Long Tasks API 的網站能繼續運作,以免干擾現有用途。
雖然 Long Tasks API 可能受益於一些 LoAF 的某些功能 (例如更好的歸因模式),但我們相信只要專注在框架上,而不是透過工作提供了許多好處,這使得它與現有 Long Tasks API 的 API 大不相同。
為什麼我沒有指令碼項目?
這可能表示長動畫影格並非由於 JavaScipt 所引起,而是產生大型轉譯工作。
如果長動畫影格「是」導致 JavaScript,但基於先前所述的各種隱私權原因 (主要是非該網頁擁有的 JavaScript),而無法提供指令碼屬性,也可能會發生這種情況。
為什麼我有指令碼項目,但來源資訊不是,或有限制?
可能的原因很多,包括沒有可參考的適當資料來源。
也會限制 no-cors cross-origin
指令碼的指令碼資訊,但只要在 <script>
呼叫中加入 crossOrigin = "anonymous"
來擷取這些指令碼,即可解決這個問題。
舉例來說,預設 Google 代碼管理工具指令碼可加進網頁:
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
您可以強化加入j.crossOrigin = "anonymous"
,以便向 GTM 提供完整的歸因詳細資料
這會取代 Long Tasks API 嗎?
雖然我們相信 Long Animation Frames API 更適合用於評估長時間工作,但目前並沒有淘汰 Long Tasks API。
希望您提供意見
您可以在 GitHub 問題清單中提供意見,或者透過 Chrome 的問題追蹤工具,提交關於 Chrome 實作 API 的錯誤。
結論
Long Animation Frames API 是令人興奮的新 API,與舊版 Long Tasks API 相比有許多潛在優勢。
事實證明,這是處理 INP 評估回應問題的關鍵工具。INP 是不易最佳化的指標,而這個 API 是 Chrome 團隊讓開發人員更容易辨識及解決問題的方法之一。
不過,Long Animation Frames API 的範圍不僅僅止於 INP,有助於找出更新速度較慢的其他原因,進而影響網站整體使用者體驗。