最新の Web アプリケーションでは、スムーズでパフォーマンスの高いアニメーションが不可欠です。ただし、それらを不適切に管理すると、ブラウザのメイン スレッドに過負荷がかかり、パフォーマンスの低下やアニメーションの乱れが発生する可能性があります。 requestAnimationFrame (rAF) は、アニメーションをディスプレイのリフレッシュ レートと同期するように設計されたブラウザ API で、setTimeout などの代替手段と比較してスムーズな動きを保証します。ただし、rAF を効率的に使用するには、特に複数のアニメーションを処理する場合、慎重な計画が必要です。
この記事では、アニメーション管理を一元化し、FPS 制御を導入し、ブラウザのメイン スレッドの応答性を維持することで requestAnimationFrame を最適化する方法を検討します。
アニメーションのパフォーマンスについて議論する場合、1 秒あたりのフレーム数 (FPS) が重要です。ほとんどの画面は 60 FPS で更新されます。これは、requestAnimationFrame が 1 秒あたり 60 回呼び出されることを意味します。スムーズなアニメーションを維持するには、ブラウザはフレームあたり約 16.67 ミリ秒以内に作業を完了する必要があります。
単一フレーム中に実行されるタスクが多すぎると、ブラウザーが目標フレーム時間を逃し、途切れやフレーム落ちが発生する可能性があります。特定のアニメーションの FPS を下げると、メインスレッドの負荷が軽減され、パフォーマンスと滑らかさのバランスが取れます。
アニメーションをより効率的に管理するには、コード全体に複数の requestAnimationFrame 呼び出しを分散させるのではなく、共有ループを使用してアニメーションの処理を一元化できます。一元化されたアプローチにより、冗長な呼び出しが最小限に抑えられ、FPS コントロールの追加が容易になります。
次のAnimationManagerクラスを使用すると、ターゲットFPSを制御しながらアニメーションタスクを登録および登録解除できます。デフォルトでは 60 FPS を目指していますが、これはパフォーマンスのニーズに合わせて調整できます。
class AnimationManager { private tasks: Set= 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 に基づいて次の更新までに十分な時間が経過したかどうかを判断します。これにより、更新の頻度を調整して、ブラウザのメインスレッドが過負荷にならないようにすることができます。
3 つのボックスをそれぞれ異なるアニメーションでアニメーション化する例を作成してみましょう。1 つはスケールし、もう 1 つは色を変更し、3 つ目は回転します。
HTML は次のとおりです:
CSS は次のとおりです:
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
次に、JavaScript を追加して、異なるプロパティで各ボックスをアニメーション化します。 1 つは拡大縮小し、もう 1 つは色を変更し、3 つ目は回転します。
ステップ 1: 線形補間の追加 (lerp)
線形補間 (lerp) は、2 つの値の間をスムーズに遷移するためにアニメーションで使用される一般的な手法です。これにより、段階的かつスムーズな進行を実現できるため、時間の経過とともにプロパティを拡大縮小、移動、または変更するのに最適です。この関数は、開始値、終了値、および遷移の距離を決定する正規化された時間 (t) の 3 つのパラメータを取ります。
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: カラー アニメーション
次に、2 番目のボックスの色の変化をアニメーション化します:
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: 回転アニメーション
最後に、3 番目のボックスを回転する関数を作成します:
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: アニメーションの開始
最後に、3 つのボックスすべてのアニメーションを開始できます:
// 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 リポジトリ: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3