Home >Web Front-end >JS Tutorial >Supercharge Your Web Animations: Optimize requestAnimationFrame Like a Pro
流暢且高效能的動畫在現代 Web 應用程式中至關重要。然而,管理不當可能會使瀏覽器的主執行緒過載,導致效能不佳和動畫卡頓。 requestAnimationFrame (rAF) 是一種瀏覽器 API,旨在將動畫與顯示器的刷新率同步,從而確保與 setTimeout 等替代方案相比更流暢的運動。但高效使用 rAF 需要仔細規劃,尤其是在處理多個動畫時。
在本文中,我們將探討如何透過集中動畫管理、引入 FPS 控制以及保持瀏覽器主執行緒回應來最佳化 requestAnimationFrame。
在討論動畫效能時,每秒影格數 (FPS) 至關重要。大多數螢幕以 60 FPS 刷新,這意味著 requestAnimationFrame 每秒被呼叫 60 次。為了保持流暢的動畫,瀏覽器必須在每幀約 16.67 毫秒內完成其工作。
如果在單一影格中執行太多任務,瀏覽器可能會錯過其目標幀時間,從而導致卡頓或丟幀。降低某些動畫的 FPS 有助於減少主執行緒的負載,從而在效能和流暢度之間取得平衡。
為了更有效地管理動畫,我們可以透過共享循環集中處理動畫,而不是在程式碼中分散多個 requestAnimationFrame 呼叫。集中式方法可最大程度地減少冗餘調用,並更輕鬆地添加 FPS 控制。
下面的AnimationManager類別允許我們在控制目標FPS的同時註冊和取消註冊動畫任務。預設情況下,我們的目標是 60 FPS,但這可以根據效能需求進行調整。
class AnimationManager { private tasks: Set<FrameRequestCallback> = new Set(); private fps: number = 60; // Target FPS private lastFrameTime: number = performance.now(); private animationId: number | null = null; // Store the animation frame ID private run = (currentTime: number) => { const deltaTime = currentTime - this.lastFrameTime; // Ensure the tasks only run if enough time has passed to meet the target FPS if (deltaTime > 1000 / this.fps) { this.tasks.forEach((task) => task(currentTime)); this.lastFrameTime = currentTime; } this.animationId = requestAnimationFrame(this.run); }; public registerTask(task: FrameRequestCallback) { this.tasks.add(task); if (this.tasks.size === 1) { this.animationId = requestAnimationFrame(this.run); // Start the loop if this is the first task } } public unregisterTask(task: FrameRequestCallback) { this.tasks.delete(task); if (this.tasks.size === 0 && this.animationId !== null) { cancelAnimationFrame(this.animationId); // Stop the loop if no tasks remain this.animationId = null; // Reset the ID } } } export const animationManager = new AnimationManager();
在此設定中,我們計算幀之間的 deltaTime,以確定基於目標 FPS 是否已經過去了足夠的時間進行下一次更新。這使我們能夠限制更新頻率,以確保瀏覽器的主執行緒不會過載。
讓我們建立一個範例,其中我們為三個盒子設定動畫,每個盒子都有不同的動畫:一個縮放,另一個改變顏色,第三個旋轉。
這是 HTML:
<div id="animate-box-1" class="animated-box"></div> <div id="animate-box-2" class="animated-box"></div> <div id="animate-box-3" class="animated-box"></div>
這是 CSS:
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
現在,我們將新增 JavaScript 來為每個具有不同屬性的方塊設定動畫。一個會縮放,另一個會改變顏色,第三個會旋轉。
第 1 步:新增線性內插 (lerp)
線性插值 (lerp) 是動畫中常用的技術,用於在兩個值之間平滑過渡。它有助於創建漸進且平滑的進程,使其成為隨時間推移縮放、移動或更改屬性的理想選擇。此函數採用三個參數:起始值、結束值和標準化時間 (t),該時間決定過渡的距離。
function lerp(start: number, end: number, t: number): number { return start + (end - start) * t; }
第 2 步:縮放動畫
我們先建立一個函數來為第一個框的縮放設定動畫:
function animateScale( scaleBox: HTMLDivElement, startScale: number, endScale: number, speed: number ) { let scaleT = 0; function scale() { scaleT += speed; if (scaleT > 1) scaleT = 1; const currentScale = lerp(startScale, endScale, scaleT); scaleBox.style.transform = `scale(${currentScale})`; if (scaleT === 1) { animationManager.unregisterTask(scale); } } animationManager.registerTask(scale); }
第 3 步:彩色動畫
接下來,我們為第二個框的顏色變化設定動畫:
function animateColor( colorBox: HTMLDivElement, startColor: number, endColor: number, speed: number ) { let colorT = 0; function color() { colorT += speed; if (colorT > 1) colorT = 1; const currentColor = Math.floor(lerp(startColor, endColor, colorT)); colorBox.style.backgroundColor = `rgb(${currentColor}, 100, 100)`; if (colorT === 1) { animationManager.unregisterTask(color); } } animationManager.registerTask(color); }
第 4 步:旋轉動畫
最後,我們建立旋轉第三個框的函數:
function animateRotation( rotateBox: HTMLDivElement, startRotation: number, endRotation: number, speed: number ) { let rotationT = 0; function rotate() { rotationT += speed; // Increment progress if (rotationT > 1) rotationT = 1; const currentRotation = lerp(startRotation, endRotation, rotationT); rotateBox.style.transform = `rotate(${currentRotation}deg)`; // Unregister task once the animation completes if (rotationT === 1) { animationManager.unregisterTask(rotate); } } animationManager.registerTask(rotate); }
第 5 步:開始動畫
最後,我們可以啟動所有三個盒子的動畫:
// Selecting the elements const scaleBox = document.querySelector("#animate-box-1") as HTMLDivElement; const colorBox = document.querySelector("#animate-box-2") as HTMLDivElement; const rotateBox = document.querySelector("#animate-box-3") as HTMLDivElement; // Starting the animations animateScale(scaleBox, 1, 1.5, 0.02); // Scaling animation animateColor(colorBox, 0, 255, 0.01); // Color change animation animateRotation(rotateBox, 360, 1, 0.005); // Rotation animation
使用 requestAnimationFrame 時,必須記住動畫在瀏覽器的主執行緒上運行。主執行緒超載過多的任務可能會導致瀏覽器錯過動畫幀,從而導致卡頓。這就是為什麼使用集中式動畫管理器和 FPS 控制等工具來最佳化動畫可以幫助保持流暢度,即使有多個動畫也是如此。
在 JavaScript 中有效管理動畫需要的不僅僅是使用 requestAnimationFrame。透過集中動畫並控制 FPS,您可以確保動畫更流暢、效能更高,同時保持主執行緒回應能力。在此範例中,我們展示如何使用單一 AnimationManager 處理多個動畫,示範如何最佳化效能和可用性。雖然為了簡單起見,我們專注於保持一致的 FPS,但這種方法可以擴展到處理各種動畫的不同 FPS 值,儘管這超出了本文的範圍。
Github Repo: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
The above is the detailed content of Supercharge Your Web Animations: Optimize requestAnimationFrame Like a Pro. For more information, please follow other related articles on the PHP Chinese website!