CSS 和 JavaScript 動畫效能
動畫對於許多應用程式的愉悅使用者體驗至關重要。實現 Web 動畫的方法有很多,例如 CSS transitions/animations 或基於 JavaScript 的動畫(使用 requestAnimationFrame())。本文將分析基於 CSS 的動畫和基於 JavaScript 的動畫之間的效能差異。
CSS 過渡和動畫
CSS 過渡和動畫均可用於編寫動畫。它們各自有不同的使用場景。
- CSS
transitions提供了一種簡單的方法,使動畫在當前樣式和最終 CSS 狀態之間發生,例如,靜止按鈕狀態和懸停狀態。即使元素處於過渡過程的中間,新的過渡也會立即從當前樣式開始,而不是跳到最終 CSS 狀態。有關更多詳細資訊,請參閱 使用 CSS 過渡。 - 另一方面,CSS
animations允許開發人員在初始屬性值集合和最終屬性值集合之間建立動畫,而不是在兩個狀態之間。CSS 動畫包含兩個組成部分:描述 CSS 動畫的樣式以及指示動畫樣式的開始和結束狀態以及可能存在的中間點的關鍵幀集。有關更多詳細資訊,請參閱 使用 CSS 動畫。
在效能方面,使用 CSS 過渡或動畫實現動畫之間沒有區別。在本文中,它們都歸類為相同的基於 CSS 的方法。
requestAnimationFrame
requestAnimationFrame() API 提供了一種在 JavaScript 中建立動畫的有效方法。該方法的回撥函式在每次幀重繪之前由瀏覽器呼叫。與需要指定延遲引數的 setTimeout()/setInterval() 相比,requestAnimationFrame() 的效率更高。開發人員可以透過在每次迴圈呼叫時更改元素的樣式(或更新 Canvas 繪製,或執行其他操作)來建立動畫。
注意: 與 CSS 過渡和動畫類似,requestAnimationFrame() 在當前選項卡被移至後臺時會暫停。
有關更多詳細資訊,請閱讀 使用 JavaScript 從 setInterval 到 requestAnimationFrame 進行動畫。
效能比較
過渡與 requestAnimationFrame
事實上,在大多數情況下,基於 CSS 的動畫的效能幾乎與基於 JavaScript 的動畫相同 - 至少在 Firefox 中是這樣。一些基於 JavaScript 的動畫庫(例如 GSAP 和 Velocity.JS)甚至聲稱它們能夠實現比 原生 CSS 過渡/動畫 更好的效能。這是因為在每次重繪事件發生之前,CSS 過渡/動畫會在主 UI 執行緒中對元素樣式進行重取樣,這與透過在下一個重繪之前觸發的 requestAnimationFrame() 回撥重取樣元素樣式幾乎相同。如果兩種動畫都在主 UI 執行緒中建立,那麼在效能方面沒有區別。
在本節中,我們將使用 Firefox 逐步指導您完成效能測試,以檢視哪種動畫方法總體上更好。
啟用 FPS 工具
在檢視示例之前,請先啟用 FPS 工具以檢視當前幀率。
- 在 URL 位址列中,輸入about:config;單擊
我保證小心!按鈕進入配置螢幕。
- 在搜尋欄中,搜尋
layers.acceleration.draw-fps偏好設定。 - 雙擊該條目將值設定為
true。現在,您將在 Firefox 視窗的左上角看到三個紫色的小方框。第一個方框代表 FPS。
執行效能測試
在下面看到的測試中,最初使用 CSS 動畫對總共 1000 個 <div> 元素進行轉換。
const boxes = [];
const button = document.getElementById("toggle-button");
const boxContainer = document.getElementById("box-container");
const animationType = document.getElementById("type");
// create boxes
for (let i = 0; i < 1000; i++) {
const div = document.createElement("div");
div.classList.add("css-animation");
div.classList.add("box");
boxContainer.appendChild(div);
boxes.push(div.style);
}
let toggleStatus = true;
let rafId;
button.addEventListener("click", () => {
if (toggleStatus) {
animationType.textContent = " requestAnimationFrame";
for (const child of boxContainer.children) {
child.classList.remove("css-animation");
}
rafId = window.requestAnimationFrame(animate);
} else {
window.cancelAnimationFrame(rafId);
animationType.textContent = " CSS animation";
for (const child of boxContainer.children) {
child.classList.add("css-animation");
}
}
toggleStatus = !toggleStatus;
});
const duration = 6000;
const translateX = 500;
const rotate = 360;
const scale = 1.4 - 0.6;
let start;
function animate(time) {
if (!start) {
start = time;
rafId = window.requestAnimationFrame(animate);
return;
}
const progress = (time - start) / duration;
if (progress < 2) {
let x = progress * translateX;
let transform;
if (progress >= 1) {
x = (2 - progress) * translateX;
transform = `translateX(${x}px) rotate(${
(2 - progress) * rotate
}deg) scale(${0.6 + (2 - progress) * scale})`;
} else {
transform = `translateX(${x}px) rotate(${progress * rotate}deg) scale(${
0.6 + progress * scale
})`;
}
for (const box of boxes) {
box.transform = transform;
}
} else {
start = null;
}
rafId = window.requestAnimationFrame(animate);
}
可以透過單擊切換按鈕將動畫切換到requestAnimationFrame()。
現在嘗試執行這兩個測試,比較每個測試的 FPS(第一個紫色方框)。您應該會發現 CSS 動畫和requestAnimationFrame() 的效能非常接近。
非主執行緒動畫
即使考慮到上述測試結果,我們仍然認為 CSS 動畫是更好的選擇。但為什麼呢?關鍵在於,只要我們要動畫化的屬性不會觸發重排/重繪(閱讀 CSS 觸發器 以瞭解更多資訊),我們就可以將這些取樣操作移出主執行緒。最常見的屬性是 CSS transform。如果一個元素被提升為 層,則可以在 GPU 中對 transform 屬性進行動畫,這意味著更好的效能/效率,尤其是在移動裝置上。在 非主執行緒合成 中可以找到更多詳細資訊。
若要啟用 Firefox 中的 OMTA(非主執行緒動畫),您可以轉到about:config 並搜尋layers.offmainthreadcomposition.async-animations。將其值切換到true。
啟用 OMTA 後,再次嘗試執行上面的測試。您應該會發現 CSS 動畫的 FPS 會顯著提高。
注意: 在 Nightly/Developer Edition 中,您應該會看到 OMTA 預設啟用,因此您可能需要反過來進行測試(先啟用它進行測試,然後停用它以進行無 OMTA 測試)。
總結
瀏覽器能夠最佳化渲染流程。總之,我們應該始終嘗試使用 CSS 過渡/動畫來建立動畫(如果可能)。如果您的動畫非常複雜,則可能需要依賴基於 JavaScript 的動畫。