長動畫幀計時

長動畫幀(Long Animation Frames,LoAFs)會影響網站的使用者體驗。它們可能導致使用者介面(UI)更新緩慢,從而使得控制元件看似無響應,動畫效果和滾動出現卡頓(或不流暢),進而引起使用者沮喪。長動畫幀 API 允許開發者獲取關於長動畫幀的資訊,從而更好地理解其根本原因。本文將展示如何使用長動畫幀 API。

什麼是長動畫幀?

長動畫幀 (LoAF) 是指渲染更新延遲超過 50 毫秒的情況。

良好的響應能力意味著頁面能夠快速響應互動。這包括及時繪製使用者所需的任何更新,並避免任何可能阻礙這些更新的情況。例如,Google 的互動到下一幀渲染 (INP) 指標建議網站應在 200 毫秒內響應頁面互動(如點選或按鍵)。

為了使動畫流暢,更新必須快速——為了使動畫以每秒 60 幀流暢執行,每個動畫幀應在大約 16 毫秒內渲染(1000/60)。

觀察長動畫幀

要獲取 LoAF 的資訊並找出問題所在,您可以使用標準的 PerformanceObserver 觀察 entryType"long-animation-frame" 的效能時間線條目。

js
const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: "long-animation-frame", buffered: true });

也可以使用 Performance.getEntriesByType() 等方法查詢先前的長動畫幀。

js
const loafs = performance.getEntriesByType("long-animation-frame");

但是請注意,"long-animation-frame" 條目型別的最大緩衝區大小為 200,超過此限制後新條目將被丟棄,因此建議使用 PerformanceObserver 方法。

檢查 "long-animation-frame" 條目

型別為 "long-animation-frame" 的效能時間線條目由 PerformanceLongAnimationFrameTiming 物件表示。此物件具有一個 scripts 屬性,其中包含一個 PerformanceScriptTiming 物件的陣列,每個物件都包含有關導致長動畫幀的指令碼的資訊。

以下是一個完整的 "long-animation-frame" 效能條目示例,其中包含單個指令碼。

js
({
  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,
});

除了 PerformanceEntry 條目返回的標準資料外,它還包含以下值得注意的項:

blockingDuration

一個 DOMHighResTimeStamp,表示主執行緒被阻塞,無法響應高優先順序任務(例如使用者輸入)的總毫秒時間。它的計算方式是:獲取 LoAF 中所有持續時間超過 50ms長任務,從每個任務中減去 50ms,將渲染時間加到最長任務時間上,然後將結果求和。

firstUIEventTimestamp

一個 DOMHighResTimeStamp,表示在當前動畫幀期間處理的第一個 UI 事件(例如滑鼠或鍵盤事件)的時間。請注意,如果事件發生和處理之間存在延遲,此時間戳可能在動畫幀開始之前。

renderStart

一個 DOMHighResTimeStamp,表示渲染週期的開始時間,包括 Window.requestAnimationFrame() 回撥、樣式和佈局計算、ResizeObserver 回撥以及 IntersectionObserver 回撥。

styleAndLayoutStart

一個 DOMHighResTimeStamp,表示當前動畫幀的樣式和佈局計算開始的時間段。

PerformanceScriptTiming 屬性

提供有關導致 LoAF 的指令碼資訊的屬性。

script.executionStart

一個 DOMHighResTimeStamp,表示指令碼編譯完成並開始執行的時間。

script.forcedStyleAndLayoutDuration

一個 DOMHighResTimeStamp,表示指令碼處理強制佈局/樣式所花費的總毫秒時間。請參閱避免佈局抖動以瞭解其原因。

script.invokerscript.invokerType

字串值,表示指令碼是如何呼叫的(例如,"IMG#id.onload""Window.requestAnimationFrame"),以及指令碼入口點型別(例如,"event-listener""resolve-promise")。

script.pauseDuration

一個 DOMHighResTimeStamp,表示指令碼在“暫停”同步操作(例如,Window.alert() 呼叫或同步 XMLHttpRequest)上花費的總毫秒時間。

script.sourceCharPositionscript.sourceFunctionNamescript.sourceURL

分別表示指令碼字元位置、函式名和指令碼 URL 的值。需要注意的是,報告的函式名將是指令碼的“入口點”(即堆疊的頂層),而不是任何特定的慢速子函式。

例如,如果事件處理程式呼叫一個頂層函式,而該頂層函式又呼叫一個慢速子函式,則 source* 欄位將報告頂層函式的名稱和位置,而不是慢速子函式。這是出於效能原因——完整的堆疊跟蹤開銷很大。

script.windowAttributionscript.window

一個列舉值,描述了該指令碼執行所在的容器(即頂層文件或