瞭解如何評估動畫、如何思考動畫影格,以及整體網頁流暢度。
您可能曾經遇到在捲動或動畫期間,網頁出現「卡頓」或「凍結」的情形。我們認為這些體驗並非「順暢」。為解決這類問題,Chrome 團隊一直致力於為實驗室工具新增更多動畫偵測支援功能,並持續改善 Chromium 中的轉譯管道診斷功能。
我們想分享近期的進展、提供具體工具指南,並討論日後動畫流暢度指標的相關構想。一如往常,我們十分歡迎您提供意見回饋。
這篇文章將涵蓋三個主要主題:
- 快速瀏覽動畫和動畫影格。
- 我們目前對評估整體動畫流暢度的想法。
- 以下提供幾項實用建議,供您在實驗室工具中使用。
什麼是動畫?
動畫可讓內容更生動!動畫可讓內容移動,尤其是在回應使用者互動時,讓體驗更自然、易懂且有趣。
但如果動畫實作不良,或是加入過多動畫,可能會導致使用體驗變差,甚至完全沒有樂趣。我們都曾經歷過使用介面加入過多「實用」轉場效果的情況,但這些效果在效能不佳時,反而會讓使用者體驗變得更糟。因此,部分使用者可能會偏好減少動畫效果,您應尊重使用者的偏好設定。
動畫如何運作?
快速複習一下,轉譯管道包含以下幾個連續的階段:
- 樣式:計算套用至元素的樣式。
- 版面配置:產生每個元素的幾何圖形和位置。
- 繪圖:將每個元素的像素填入圖層。
- 合成:將圖層繪製至螢幕。
雖然定義動畫的方式有很多種,但基本上都是透過下列其中一種方式運作:
- 調整版面配置屬性。
- 調整paint 屬性。
- 調整組合屬性。
由於這些階段是依序進行,因此請務必根據管道中較後面的屬性定義動畫。在過程中越早更新,成本就越高,且不太可能順利完成。(詳情請參閱「算繪效能」)。
雖然為版面配置屬性設定動畫效果很方便,但這麼做還是會產生成本,即使這些成本並非立即顯現也一樣。動畫應盡可能以複合屬性變更定義。
定義宣告式 CSS 動畫或使用 Web Animations,並確保動畫合成屬性,是確保動畫流暢且有效率的絕佳第一步。不過,即使是效能高效的網頁動畫,也存在效能限制,因此這項做法並不能保證流暢度。因此,評估成效十分重要!
什麼是動畫影格?
頁面視覺呈現的更新內容需要一段時間才會顯示。視覺變更會產生新的動畫影格,並最終在使用者的螢幕上顯示。
顯示器會在某個間隔後更新,因此視覺更新會分批處理。許多螢幕會以固定的時間間隔更新,例如每秒 60 次 (即 60 Hz)。部分較新的螢幕可提供更高的刷新率 (90 到 120 Hz 的刷新率越來越普遍)。這些螢幕通常可視需要在不同螢幕更新率之間主動調整,甚至提供完全可變的幀率。
任何應用程式 (例如遊戲或瀏覽器) 的目標都是處理所有這些已批次處理的視覺更新,並在每次期限內產生完整的視覺動畫影格。請注意,這個目標與其他重要的瀏覽器工作完全不同,例如快速載入網路內容或有效執行 JavaScript 工作。
在某些情況下,要完成所有視覺更新,可能會超出螢幕指定的期限。在這種情況下,瀏覽器會捨棄影格。螢幕不會變黑,只是不斷重複顯示相同畫面。您會看到相同的視覺更新內容,也就是先前影格機會中顯示的動畫影格。
這種情況其實很常見!甚至可能不會察覺到,尤其是在網路平台上常見的靜態或類似文件的內容。只有在出現重要的視覺更新 (例如動畫) 時,才會明顯看出掉格情形,因為我們需要持續更新動畫,才能呈現流暢的動作。
哪些因素會影響動畫影格?
網路開發人員可以大幅影響瀏覽器快速且有效率地算繪及顯示視覺更新的功能!
以下提供一些例子:
- 使用過大或耗用大量資源的內容,無法在目標裝置上快速解碼。
- 使用過多圖層,需要太多 GPU 記憶體。
- 定義過於複雜的 CSS 樣式或網頁動畫。
- 使用會停用快速算繪最佳化的設計反模式。
- 主執行緒上的 JS 工作過多,導致長時間工作阻斷視覺更新。
不過,要如何得知動畫影格是否已錯過期限,並導致影格遺漏?
其中一種方法是使用 requestAnimationFrame()
輪詢,但這有幾個缺點。requestAnimationFrame()
或「rAF」會告知瀏覽器您想執行動畫,並要求在轉譯管道的下一個繪圖階段前執行。如果回呼函式未在預期時間內呼叫,表示系統未執行著色作業,且略過一或多個影格。您可以透過輪詢並計算 rAF 的呼叫頻率,計算出「每秒影格數」(FPS) 指標。
let frameTimes = [];
function pollFramesPerSecond(now) {
frameTimes = [...frameTimes.filter(t => t > now - 1000), now];
requestAnimationFrame(pollFramesPerSecond);
console.log('Frames per second:', frameTimes.length);
}
requestAnimationFrame(pollFramesPerSecond);
使用 requestAnimationFrame()
輪詢並非理想做法,原因如下:
- 每個指令碼都必須設定自己的輪詢迴圈。
- 這可能會阻斷關鍵路徑。
- 即使 rAF 輪詢速度很快,但如果持續使用,仍會導致
requestIdleCallback()
無法排程長時間閒置的區塊 (超過單一影格)。 - 同樣地,如果沒有長時間閒置的區塊,瀏覽器就無法排程其他長時間執行的工作 (例如較長時間的垃圾收集作業,以及其他背景或推測工作)。
- 如果您切換檢查功能,就會錯過超出影格預算的情況。
- 如果瀏覽器使用變化更新頻率 (例如因電源或可見度狀態而異動),輪詢就會回報誤判。
- 最重要的是,它實際上並未擷取所有類型的動畫更新!
主執行緒上的工作過多,可能會影響動畫影格的顯示能力。請查看Jank 範例,瞭解在主執行緒上有太多工作 (例如版面配置) 時,rAF 驅動的動畫會導致影格遺失、rAF 回呼次數減少,以及 FPS 降低。
當主執行緒變得緩慢時,視覺更新就會開始延遲。這就是卡頓!
許多評估工具都著重於主執行緒能否及時產生,以及動畫影格能否順利執行。但這並非全部!請參考以下範例:
上方的影片顯示一個網頁,該網頁會定期將長時間的工作注入至主執行緒。這些長時間的工作會完全破壞網頁提供特定類型視覺更新的功能,您可以在左上角看到 requestAnimationFrame()
回報的 FPS 數量相應下降至 0。
儘管有這些長時間的作業,頁面仍可順暢捲動。這是因為在現代瀏覽器中,捲動通常是多執行緒的,完全由合成器驅動。
這個範例同時包含主執行緒上許多遺漏的影格,但仍有許多成功傳送的捲動影格在合成器執行緒上。長時間執行的工作完成後,主執行緒的繪圖更新作業不會提供任何視覺變化。rAF 輪詢建議將影格數降至 0,但從視覺角度來看,使用者不會發現任何差異!
但動畫影格的故事就沒那麼簡單。
動畫影格:重要更新
上述範例顯示,故事不只包含 requestAnimationFrame()
。
那麼,動畫更新和動畫影格何時會影響動畫?以下是我們正在考慮的部分條件,歡迎提供寶貴意見:
- 主執行緒和合成器執行緒更新
- 缺少繪圖更新
- 偵測動畫
- 品質與數量
主執行緒和合成器執行緒更新
動畫影格更新不是布林值。並非只有影格可能完全遺漏或完全顯示。動畫影格可能會部分 顯示,原因有很多。換句話說,它可以同時顯示部分過時的內容,以及一些新的視覺更新。
最常見的例子是,瀏覽器無法在影格截止時間內產生新的主執行緒更新,但確實有新的轉譯器執行緒更新 (例如前述的執行緒捲動範例)。
建議使用宣告式動畫為合成屬性製作動畫,其中一個重要原因是,這樣一來,即使主執行緒忙碌,動畫也能完全由合成器執行緒驅動。這類動畫可繼續並行有效產生視覺更新。
另一方面,在某些情況下,主執行緒更新可能會在錯過幾個影格期限後,才最終可供呈現。瀏覽器會更新,但可能不是最新版本。
一般來說,我們會將包含部分新視覺更新 (而非所有新視覺更新) 的框架視為部分框架。部分影格相當常見。理想情況下,部分更新至少會包含最重要的視覺更新,例如動畫,但只有在動畫由合成器執行緒驅動時,才會發生這種情況。
缺少繪圖更新
另一種部分更新是圖片等媒體未在時間內完成解碼和光柵處理,無法在時間內呈現影格。
或者,即使網頁完全靜態,瀏覽器在快速捲動期間仍可能無法即時更新視覺效果。這是因為可視視窗之外的內容像素呈現可能會遭到捨棄,以節省 GPU 記憶體。轉譯像素需要時間,而且在進行大範圍捲動 (例如手指滑動) 後,轉譯所有內容可能需要的時間會比單一影格還要長。這通常稱為「棋盤式顯示」。
每個影格算繪機會都會追蹤最新視覺更新內容實際顯示在螢幕上的比例。測量在多個影格 (或時間) 中執行此操作的能力,通常稱為「影格傳輸量」。
如果 GPU 確實卡住,瀏覽器 (或平台) 甚至可能會開始限制其嘗試視覺更新的速度,進而降低有效影格速率。雖然從技術層面來說,這麼做可以減少遺漏的影格更新次數,但從視覺層面來看,這麼做仍會導致影格傳輸量降低。
不過,並非所有類型的低影格傳輸率都會造成不良影響。如果網頁大多處於閒置狀態,且沒有任何活動動畫,低影格速率的視覺效果就會與高影格速率一樣吸引人 (而且還能省電)。
那麼,何時才會影響影格輸出率?
偵測動畫
在有重要動畫的期間,高影格傳輸量就顯得格外重要。不同動畫類型會依賴特定執行緒 (主執行緒、合成器或工作執行緒) 提供的視覺更新,因此視覺更新會依賴該執行緒在期限內提供更新。我們會說,每當有依賴該執行緒更新的活動動畫時,該執行緒就會影響流暢度。
某些類型的動畫比其他類型更容易定義及偵測。宣告式動畫 (或使用者輸入驅動的動畫) 定義較為明確,而 JavaScript 驅動的動畫則是透過定期更新動畫樣式屬性來實作。
即使使用 requestAnimationFrame()
,您也無法一概而論,認為每個 rAF 呼叫都會產生視覺更新或動畫。舉例來說,如果您只使用 rAF 輪詢來追蹤影格速率 (如上圖所示),由於沒有視覺更新,因此不應影響流暢度評估。
品質與數量
最後,偵測動畫和動畫影格更新只是故事的一部分,因為它只擷取動畫更新的數量,而非品質。
舉例來說,觀看影片時,你可能會看到穩定的 60 fps 影格速率。從技術層面來說,這項功能運作得非常順暢,但影片本身可能有低位元率或網路緩衝問題。這項問題並未直接由動畫流暢度指標捕捉,但仍可能讓使用者感到不適。
或者,使用 <canvas>
的遊戲 (甚至可能使用螢幕外畫布等技術來確保穩定的幀率),在技術上可能在動畫影格方面運作順暢,但無法將高品質的遊戲素材資源載入場景,或顯示算繪偽影。
當然,某些網站可能只是有非常糟糕的動畫 🙂?
我指的是,在當時來說,這些東西還算不錯!
單一動畫影格的狀態
由於影格可能會部分顯示,或是影格遺失的情況不會影響流暢度,因此我們開始將每個影格視為具有完整性或流暢度分數。
以下是我們解讀單一動畫影格狀態的方式,從最佳到最差情況排序:
不希望更新 | 閒置時間,重複上一個影格。 |
完全呈現 | 主執行緒更新已在期限內提交,或是不需要主執行緒更新。 |
部分呈現 | 僅限合成器;延遲的主執行緒更新並未有任何視覺變化。 |
部分呈現 | 僅限合成器;主執行緒有視覺更新,但該更新並未包含會影響流暢度的動畫。 |
部分呈現 | 僅限合成器:主執行緒有視覺更新,會影響流暢度,但先前已過時的影格已到達並取代。 |
部分呈現 | 僅限合成器;沒有所需的主要更新,且合成器更新包含會影響流暢度的動畫。 |
部分呈現 | 僅限合成器,但合成器更新沒有影響流暢度的動畫。 |
捨棄的畫格 | 沒有更新。沒有需要的轉換器更新,且主程式延遲。 |
捨棄的畫格 | 需要進行合成器更新,但延遲了。 |
過時影格 | 需要更新,且已由轉譯器產生,但 GPU 仍未在 vsync 期限前顯示。 |
您可以將這些狀態轉換為某種分數。或許您可以將這個分數視為使用者觀察到該事件的機率。單一影格遺失的情況可能不易察覺,但連續多個影格遺失的情況,肯定會影響畫面流暢度!
總結:影格遺失百分比指標
雖然有時需要深入瞭解每個動畫影格狀態,但為體驗快速指派「一目瞭然」分數也很有幫助。
由於影格可能只會部分顯示,且即使完全略過影格更新,也可能不會實際影響流暢度,因此我們不想只著重於計算影格,而是著重於瀏覽器在重要時刻無法提供完整視覺效果的程度。
心智模型應從以下內容轉變:
- 每秒影格數,
- 偵測缺少和重要的動畫更新,以便
- 指定時間範圍內的降幅百分比。
重要的是:等待重要更新的時間比例。我們認為這與使用者實際體驗網頁內容流暢度的自然方式相符。到目前為止,我們使用以下指標做為初始指標:
- 平均遺漏百分比:針對整個時間軸上所有非閒置動畫影格
- 影格遺漏百分比的極端情況:以 1 秒的滑動時間視窗來評估。
- 第 95 個百分位數的遺漏影格百分比:以 1 秒的滑動時間窗格為單位進行測量。
您現在可以在部分 Chrome 開發人員工具中找到這些分數。雖然這些指標只著重於整體影格處理量,但我們也會評估其他因素,例如影格延遲。
歡迎在開發人員工具中親自試用!
效能 HUD
Chromium 有一個簡潔的「Performance HUD」,隱藏在標記 (chrome://flags/#show-performance-metrics-hud
) 後方。您可以在其中找到 Core Web Vitals 等項目的即時分數,以及幾個實驗定義,這些定義會根據丟失影格百分比,評估動畫流暢度。
影格算繪統計資料
透過「算繪」設定,在 DevTools 中啟用「影格算繪統計資料」,即可查看新動畫影格的即時檢視畫面,並以顏色編碼區分部分更新和完全捨棄的影格更新。回報的 fps 僅適用於完全顯示的畫面。
開發人員工具效能剖析錄製功能中的影格檢視器
開發人員工具「效能」面板已久以來都有影格檢視器。不過,這項功能已與現代轉譯管道的實際運作方式略有脫節。我們最近進行了許多改善,甚至在最新的 Chrome Canary 中也做了改善,我們認為這將大大簡化動畫偵錯問題。
您會發現,現在影格檢視器中的影格與 vsync 邊界更為對齊,並根據狀態以顏色編碼。上述所有細微差異目前仍未完全視覺化,但我們預計在近期新增更多內容。
Chrome 追蹤
最後,Chrome 追蹤功能是深入探討細節的首選工具,您可以透過新的 Perfetto UI (或 about:tracing
) 記錄「網頁內容算繪」追蹤記錄,並深入瞭解 Chrome 的圖形管道。這項工作可能十分困難,但最近 Chromium 新增了一些功能,可讓這項工作變得更簡單。您可以在「Life of a Frame」文件中,查看可用的功能。
您可以透過追蹤事件明確判斷:
- 正在執行哪些動畫 (使用名為
TrackerValidation
的事件)。 - 取得動畫影格的精確時間軸 (使用名為
PipelineReporter
的事件)。 - 針對動畫更新的卡頓情形,請找出確切原因,瞭解動畫為何無法加快執行速度 (使用
PipelineReporter
事件中的事件細目)。 - 針對輸入驅動動畫,請查看取得視覺更新所需的時間 (使用名為
EventLatency
的事件)。
後續步驟
網站使用體驗核心指標計畫旨在提供指標和指南,協助您打造出色的網頁使用者體驗。實驗室指標 (例如總阻斷時間 (TBT)) 等) 對於找出並診斷潛在的互動問題至關重要。我們正計劃設計類似的實驗室指標,用於評估動畫流暢度。
我們會持續研究設計根據個別動畫影格資料設計綜合指標的想法,並隨時向您更新。
日後,我們也想設計 API,讓我們能夠在實地和實驗室中,為實際使用者評估動畫流暢度。敬請密切關注最新消息!
意見回饋
我們很高興 Chrome 推出了所有近期改善項目和開發人員工具,可用於評估動畫流暢度。請試用這些工具,並將動畫基準測試結果提供給我們,讓我們知道結果如何!
您可以將意見傳送至 web-vitals-feedback Google 群組,主旨行中請加上「[流暢度指標]」。我們非常期待聽到你的想法!