requestAnimationFrameにおけるFPSの制御方法10選

requestAnimationFrameを使ったFPS制御JS
この記事は約30分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●requestAnimationFrameとは?

みなさん、Webサイトやアプリケーションでアニメーションを実装する際、どのような方法を使っていますか?setTimeoutやsetIntervalを使ったことがある方も多いと思います。

しかし、これらの方法ではブラウザの描画サイクルとうまく同期できず、パフォーマンスが低下してしまうことがあります。

そこで登場するのが、requestAnimationFrameというJavaScript APIです。

requestAnimationFrameを使うことで、ブラウザの描画サイクルに合わせてアニメーションを実行できるようになります。

つまり、画面の更新に合わせてコードが呼び出されるため、無駄なく効率的にアニメーションを描画できるのです。

○ブラウザの描画サイクルとの同期

ブラウザは一定の間隔で画面を更新しています。

この更新のタイミングに合わせてアニメーションを実行することが、スムーズな動きを実現するポイントです。

requestAnimationFrameは、まさにこの描画サイクルに同期してコールバック関数を呼び出してくれるのです。

コールバック関数内でアニメーションの次の状態を計算し、再度requestAnimationFrameを呼び出すことで、連続的なアニメーションが実現します。

setTimeoutやsetIntervalとは異なり、requestAnimationFrameはブラウザが描画を行うタイミングでコールバックを実行するため、無駄なく効率的です。

実際のコードを見てみましょう。

function animate() {
  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

このように、requestAnimationFrameにコールバック関数を渡すことで、アニメーションループを作ることができます。

ブラウザの描画サイクルに合わせてanimate関数が呼び出され、スムーズなアニメーションが実現するのです。

○60fpsアニメーションの実現

ブラウザの描画サイクルは通常60fpsです。

つまり、1秒間に60回の描画が行われています。

requestAnimationFrameを使えば、この60fpsに合わせてアニメーションを実行できるため、滑らかで自然な動きを実現できます。

60fpsのアニメーションを維持するためには、1フレームの処理を16.67ミリ秒(1秒÷60フレーム)以内に収める必要があります。

requestAnimationFrameを使えば、この時間内に処理を完了させることができ、高いパフォーマンスを保つことができるのです。

●FPS制御の重要性

みなさん、アニメーションを実装する際、FPSという言葉を聞いたことがあるでしょうか?

FPSとは、1秒間に描画されるフレーム数(Frames Per Second)のことです。

FPSを制御することは、アニメーションのパフォーマンスを最適化する上で非常に重要なのです。

○パフォーマンスへの影響

FPSが低いと、アニメーションがカクつきや遅延が発生し、ユーザーエクスペリエンスが損なわれてしまいます。

逆に、FPSが高すぎると、必要以上にCPUやGPUに負荷がかかり、他のタスクに影響を与える可能性があります。

理想的なFPSは、ディスプレイのリフレッシュレートに合わせることです。

一般的なディスプレイは60Hzのリフレッシュレートを持っているため、60fpsが目標となります。

しかし、複雑なアニメーションや、モバイルデバイスでは、60fpsを維持するのが難しい場合もあります。

例えば、次のようなコードを考えてみましょう。

function animate() {
  // 重い処理
  for (let i = 0; i < 100000; i++) {
    // 何らかの計算
  }

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

この例では、animate関数内で重い処理を行っています。

このような処理がある場合、1フレームの描画に時間がかかってしまい、FPSが低下してしまします。

結果として、アニメーションがカクつく原因となるのです。

したがって、FPSを適切に制御し、パフォーマンスを最適化することが重要だと言えます。

次の点に注意しましょう。

  1. 1フレームの処理は16.67ミリ秒以内に収める
  2. 重い処理は分割するか、別スレッドで実行する
  3. 不要な再描画は避ける

これらを意識することで、高いFPSを維持し、スムーズなアニメーションを実現できます。

○バッテリー消費の抑制

FPSの制御は、パフォーマンスだけでなく、バッテリー消費にも影響します。

特にモバイルデバイスでは、バッテリー寿命が重要な問題です。

高いFPSでアニメーションを実行すると、CPUやGPUに高い負荷がかかり、バッテリーを多く消費してしまいます。

必要以上にFPSを上げることは、バッテリー寿命を短くする原因となります。

したがって、アニメーションを実装する際は、デバイスの性能や用途に合わせて適切なFPSを設定することが大切です。

例えば、モバイルデバイスでは30fpsで十分なケースもあります。

バッテリー消費を抑えるためのポイントは次の通りです。

  1. 必要以上にFPSを上げない
  2. アニメーションが不要な場面では、停止する
  3. レンダリングをオフスクリーンで行う

この工夫により、バッテリー消費を最小限に抑えながら、快適なアニメーションを提供できます。

それでは、いよいよFPS制御の具体的な方法について見ていきましょう。ここからはサンプルコードを交えながら、10個の手法を順番に解説していきます。

●FPS制御の10の方法

アニメーションのパフォーマンスを最適化するには、FPSを適切に制御することが重要だと前章でお伝えしましたね。

でも、具体的にはどうすればいいのでしょうか?ここからは、FPS制御の10の方法を1つずつ見ていきたいと思います。

○サンプルコード1:フレームレートの計算

まずは、フレームレートを計算する方法から始めましょう。

フレームレートを知ることは、FPS制御の第一歩です。

let lastTime = performance.now();
let frameCount = 0;

function animate(time) {
  // フレームレートを計算
  frameCount++;
  if (time - lastTime > 1000) {
    console.log(`フレームレート: ${frameCount}`);
    frameCount = 0;
    lastTime = time;
  }

  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

このコードでは、performance.now()を使って現在の時刻を取得し、1秒ごとにフレームレートを計算しています。

frameCount変数でフレーム数をカウントし、1秒経過するたびにコンソールにフレームレートを出力します。

実行すると、コンソールにフレームレートが表示されます。

これを確認することで、現在のアニメーションのパフォーマンスを把握できます。

○サンプルコード2:フレームレート制限

次に、フレームレートを制限する方法を見ていきましょう。

フレームレートを制限することで、必要以上にCPUやGPUに負荷をかけないようにできます。

const targetFPS = 30;
const frameInterval = 1000 / targetFPS;
let lastTime = performance.now();

function animate(time) {
  // フレームレートを制限
  if (time - lastTime < frameInterval) {
    requestAnimationFrame(animate);
    return;
  }
  lastTime = time;

  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

このコードでは、targetFPS変数でターゲットのフレームレートを設定しています。

ここでは30fpsに設定していますが、状況に応じて調整してください。

frameInterval変数は、1フレームの間隔を計算しています。

animate関数内で、現在の時刻とlastTimeの差がframeIntervalより小さい場合、次のフレームをリクエストして関数を抜けます。

これにより、指定したフレームレートを超えないようにアニメーションが制御されます。

○サンプルコード3:時間ベースのアニメーション

3つ目は、時間ベースのアニメーションです。

これは、経過時間に基づいてアニメーションを更新する手法です。フレームレートに依存せず、一定の速度でアニメーションを進められます。

let lastTime = performance.now();

function animate(time) {
  // 経過時間を計算
  const elapsed = time - lastTime;
  lastTime = time;

  // 経過時間に基づいてアニメーションを更新
  // 例: 1秒間で100px移動する
  const speed = 100; // px/秒
  const distance = (speed * elapsed) / 1000;
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

このコードでは、elapsed変数で前回のフレームからの経過時間を計算しています。この経過時間を使って、アニメーションの更新量を決定します。

例えば、speed変数で1秒間に100px移動するように設定し、distance変数で経過時間に応じた移動距離を計算しています。

この距離を使ってアニメーションを更新することで、フレームレートに関係なく一定の速度でアニメーションを進められます。

○サンプルコード4:デルタタイムの利用

4つ目は、デルタタイムを利用する方法です。

デルタタイムとは、前回のフレームからの経過時間のことです。

これを使うことで、フレームレートの変動に影響されないアニメーションを実装できます。

let lastTime = performance.now();

function animate(time) {
  // デルタタイムを計算
  const deltaTime = (time - lastTime) / 1000;
  lastTime = time;

  // デルタタイムに基づいてアニメーションを更新
  // 例: 1秒間で100px移動する
  const speed = 100; // px/秒
  const distance = speed * deltaTime;
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

このコードは、サンプルコード3とよく似ていますね。ただし、elapsed変数の代わりにdeltaTime変数を使っています。

deltaTimeは秒単位の経過時間です。

例えば、speed変数で1秒間に100px移動するように設定し、distance変数でデルタタイムに応じた移動距離を計算しています。

これにより、フレームレートが変動しても一定の速度でアニメーションを進められます。

○サンプルコード5:重い処理の分割

次に、重い処理を分割する方法を見ていきましょう。

複雑な計算や大量のデータ処理などの重い処理があると、フレームレートが低下してしまいます。

そこで、処理を分割して複数のフレームに分けて実行することで、パフォーマンスを改善できます。

function heavyTask() {
  // 重い処理を分割して実行
  const totalCount = 1000;
  const chunkSize = 100;
  let currentIndex = 0;

  function processChunk() {
    const endIndex = Math.min(currentIndex + chunkSize, totalCount);
    for (let i = currentIndex; i < endIndex; i++) {
      // 処理を実行
      // ...
    }
    currentIndex = endIndex;

    if (currentIndex < totalCount) {
      requestAnimationFrame(processChunk);
    }
  }

  requestAnimationFrame(processChunk);
}

function animate() {
  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

// 重い処理を開始
heavyTask();

このコードでは、heavyTask関数内で重い処理を分割して実行しています。

totalCount変数で全体の処理数を設定し、chunkSize変数で1フレームで処理する数を設定します。

processChunk関数内で、chunkSizeの数だけ処理を実行し、currentIndexを更新します。

そして、currentIndexがtotalCountより小さい場合は、次のフレームでprocessChunkを呼び出して処理を継続します。

前回に引き続き、FPS制御の方法を見ていきましょう。ここからは、不要な再描画の防止やレンダリングの一時停止など、より高度なテクニックを紹介します。

○サンプルコード6:不要な再描画の防止

アニメーションのパフォーマンスを向上させるには、不要な再描画を避けることが重要です。

再描画が多いと、CPUやGPUに負荷がかかり、フレームレートが低下してしまいます。

let isDirty = true;

function animate() {
  if (!isDirty) {
    requestAnimationFrame(animate);
    return;
  }
  isDirty = false;

  // 画面を更新する処理
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

// 状態が変更されたら、isDirtyをtrueに設定
function onStateChange() {
  isDirty = true;
}

このコードでは、isDirty変数を使って、再描画が必要かどうかを判断しています。

isDirtyがfalseの場合、画面を更新する処理をスキップします。

状態が変更された時だけ、isDirtyをtrueに設定することで、不要な再描画を防ぐことができます。

例えば、ユーザーの操作やデータの更新などがあった時に、onStateChange関数を呼び出してisDirtyをtrueにします。

これにより、変更があった時だけ画面が更新され、パフォーマンスの向上につながります。

○サンプルコード7:レンダリングの一時停止

時には、アニメーションを一時的に停止したい場合があります。

例えば、ブラウザのタブがバックグラウンドになった時や、ユーザーがページから離れた時などです。

そんな時は、レンダリングを一時停止することで、リソースを節約できます。

let isRunning = true;

function animate() {
  if (!isRunning) {
    return;
  }

  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

// アニメーションを一時停止
function pause() {
  isRunning = false;
}

// アニメーションを再開
function resume() {
  if (!isRunning) {
    isRunning = true;
    requestAnimationFrame(animate);
  }
}

// ページが非表示になった時に一時停止
document.addEventListener('visibilitychange', function() {
  if (document.hidden) {
    pause();
  } else {
    resume();
  }
});

このコードでは、isRunning変数を使ってアニメーションの実行状態を管理しています。

pause関数でisRunningをfalseにすることで、アニメーションを一時停止します。

resume関数でisRunningをtrueに戻し、requestAnimationFrameを呼び出すことで、アニメーションを再開します。

また、visibilitychangeイベントを使って、ページの表示状態の変化を検出しています。

ページが非表示になった時にpause関数を呼び出し、再び表示された時にresume関数を呼び出すことで、自動的にアニメーションを一時停止・再開できます。

実行すると、ブラウザのタブがバックグラウンドになった時にアニメーションが一時停止し、再びフォアグラウンドになった時に再開される様子が確認できるはずです。

○サンプルコード8:requestIdleCallbackの活用

requestIdleCallbackは、ブラウザがアイドル状態になった時に呼び出されるコールバック関数を登録するためのAPIです。

これを使うことで、メインスレッドが忙しい時は処理を延期し、アイドル時に実行することができます。

function animate() {
  // アニメーションの処理を記述
  // ...

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

// アイドル時に実行する処理
function idleTask(deadline) {
  while (deadline.timeRemaining() > 0) {
    // 処理を実行
    // ...
  }

  // まだ処理が残っていれば、再度idleTaskを呼び出す
  if (thereIsMoreWork()) {
    requestIdleCallback(idleTask);
  }
}

// アイドルコールバックを登録
requestIdleCallback(idleTask);

このコードでは、requestIdleCallbackを使って、アイドル時に実行する処理を登録しています。

idleTask関数内では、deadline.timeRemaining()でアイドル期間の残り時間を取得し、その間に処理を実行します。

もし処理が完了しなかった場合は、thereIsMoreWork関数で判断し、再度requestIdleCallbackを呼び出して継続処理をリクエストします。

これにより、アニメーションの実行に影響を与えずに、バックグラウンドの処理を実行できます。

ただし、requestIdleCallbackはまだ全てのブラウザでサポートされている訳ではないので、使用する際は注意が必要です。

○サンプルコード9:OffscreenCanvasの利用

OffscreenCanvasは、メインスレッドとは別のスレッドでCanvasのレンダリングを行うためのAPIです。

これを使うことで、複雑な描画処理をバックグラウンドで実行し、メインスレッドのパフォーマンスを向上させることができます。

// OffscreenCanvasを作成
const offscreen = new OffscreenCanvas(width, height);
const ctx = offscreen.getContext('2d');

// ワーカースレッドを作成
const worker = new Worker('offscreen-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);

// メインスレッドでアニメーションを実行
function animate() {
  // OffscreenCanvasの内容をメインキャンバスにコピー
  const mainCtx = document.getElementById('canvas').getContext('2d');
  mainCtx.drawImage(offscreen, 0, 0);

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);
// offscreen-worker.js
self.onmessage = function(e) {
  const canvas = e.data.canvas;
  const ctx = canvas.getContext('2d');

  function render() {
    // 描画処理を実行
    // ...

    // 次のフレームでrender()を呼び出す
    requestAnimationFrame(render);
  }

  // レンダリングを開始
  requestAnimationFrame(render);
};

このコードでは、OffscreenCanvasを作成し、それをワーカースレッドに渡しています。

ワーカースレッド内では、渡されたOffscreenCanvasに対して描画処理を行います。

メインスレッドでは、requestAnimationFrameを使ってアニメーションを実行し、各フレームでOffscreenCanvasの内容をメインのCanvasにコピーしています。

実行すると、描画処理がワーカースレッドで行われるため、メインスレッドのパフォーマンスが向上し、より滑らかなアニメーションが実現できます。

ただし、OffscreenCanvasはまだ全てのブラウザでサポートされている訳ではないので、使用する際は注意が必要です。

○サンプルコード10:WebWorkersでの処理

WebWorkersを使うことで、重い処理をバックグラウンドで実行し、メインスレッドのパフォーマンスを向上させることができます。

アニメーションに関連する処理の一部をワーカースレッドに移動することで、より滑らかなアニメーションを実現できます。

// メインスレッドでアニメーションを実行
function animate() {
  // アニメーションの処理を記述
  // ...

  // ワーカースレッドに重い処理を依頼
  worker.postMessage({ /* データ */ });

  // 次のフレームでanimate()を呼び出す
  requestAnimationFrame(animate);
}

// アニメーションを開始
requestAnimationFrame(animate);

// ワーカースレッドを作成
const worker = new Worker('animation-worker.js');
worker.onmessage = function(e) {
  // ワーカースレッドから結果を受け取る
  const result = e.data;
  // ...
};
// animation-worker.js
self.onmessage = function(e) {
  // 重い処理を実行
  const result = heavyTask(e.data);

  // 結果をメインスレッドに返す
  self.postMessage(result);
};

function heavyTask(data) {
  // 重い処理の内容
  // ...
  return result;
}

このコードでは、メインスレッドでアニメーションを実行しつつ、重い処理をワーカースレッドに依頼しています。

ワーカースレッド内では、渡されたデータを使って重い処理を実行し、結果をメインスレッドに返します。

メインスレッドでは、ワーカースレッドから結果を受け取り、それを使ってアニメーションを更新します。

実行すると、重い処理がワーカースレッドで実行されるため、メインスレッドがブロックされることなく、滑らかなアニメーションが維持できます。

ただし、WebWorkersはメインスレッドとは別のコンテキストで実行されるため、DOM操作はできないなどの制限があることに注意してください。

●よくあるエラーと対処法

さて、ここまでrequestAnimationFrameを使ったFPS制御の方法を10個のサンプルコードを交えて解説してきました。

でも、実際にアニメーションを実装する際には、思わぬエラーに遭遇することもあるでしょう。

そこで、ここでは、よくあるエラーとその対処法を3つ紹介します。

これを理解することで、スムーズにアニメーションを実装できるようになるはずです。

○フレームレートが不安定になる

アニメーションを実装していると、フレームレートが不安定になることがあります。

特に、重い処理を実行している場合や、多くの要素を同時にアニメーションさせている場合に起こりやすいです。

フレームレートが不安定になる原因は、主に次の2つです。

  1. 1フレームの処理に時間がかかりすぎている
  2. メインスレッドがブロックされている

これらを解決するには、次のような対処法が有効です。

  1. 重い処理は分割して実行する(サンプルコード5参照)
  2. 不要な再描画は避ける(サンプルコード6参照)
  3. WebWorkersを活用して処理を分散する(サンプルコード10参照)

例えば、次のようなコードを考えてみましょう。

function animate() {
  // 重い処理
  for (let i = 0; i < 100000; i++) {
    // 何らかの計算
  }

  // 多くの要素をアニメーション
  for (let i = 0; i < 1000; i++) {
    // 要素を移動
    elements[i].style.transform = `translateX(${Math.sin(Date.now() / 1000) * 100}px)`;
  }

  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

このコードでは、重い処理と多くの要素のアニメーションを1フレーム内で実行しています。

そのため、フレームレートが不安定になってしまいます。

これを改善するには、重い処理を分割し、WebWorkersを使って別スレッドで実行するのが効果的です。

また、アニメーションする要素を減らしたり、CSSアニメーションを使ったりすることで、パフォーマンスを向上させることができます。

○アニメーションがカクつく

フレームレートが安定していても、アニメーションがカクつくことがあります。

これは、次のような原因が考えられます。

  1. repaints(再描画)とreflows(リフロー)が多く発生している
  2. CSSアニメーションとJavaScriptアニメーションが混在している
  3. 非同期処理によってメインスレッドがブロックされている

これらを解決するには、次のような対処法が有効です。

  1. CSSアニメーションを使う
  2. will-change プロパティを使って最適化する
  3. 非同期処理はPromiseやasync/awaitを使う

例えば、JavaScriptでアニメーションを実装する代わりに、CSSアニメーションを使うことで、パフォーマンスを大幅に向上させることができます。

.animated-element {
  animation: move 1s linear infinite;
}

@keyframes move {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(100px);
  }
}

このように、CSSアニメーションを使えば、ブラウザがネイティブにアニメーションを処理してくれるため、JavaScriptによる制御よりも滑らかになります。

また、will-change プロパティを使って、アニメーションする要素を事前にブラウザに伝えておくことで、最適化することもできます。

.animated-element {
  will-change: transform;
}

これにより、ブラウザはその要素の変化に備えて最適化を行い、パフォーマンスが向上します。

○メモリリークが発生する

アニメーションを長時間実行していると、メモリリークが発生することがあります。

これは、不要になったオブジェクトがメモリから解放されずに蓄積していくことが原因です。

メモリリークが発生すると、アプリケーションのパフォーマンスが低下したり、最悪の場合はクラッシュしたりします。

メモリリークを防ぐには、次のような対処法が有効です。

  1. 不要になったイベントリスナーを解除する
  2. タイマーをクリアする
  3. 参照を適切に管理する

例えば、イベントリスナーを使っている場合は、不要になったタイミングで解除することが重要です。

const element = document.getElementById('target');
const handleClick = () => {
  // 処理
};

element.addEventListener('click', handleClick);

// 不要になったら解除
element.removeEventListener('click', handleClick);

同様に、setIntervalやsetTimeoutを使っている場合は、不要になったらclearIntervalやclearTimeoutを呼び出してタイマーをクリアしましょう。

また、オブジェクトへの参照を適切に管理することも大切です。

不要になったオブジェクトへの参照を残していると、ガベージコレクションの対象にならずにメモリリークの原因になります。

●requestAnimationFrameの応用例

これまで、requestAnimationFrameを使ったFPS制御の方法や、よくあるエラーと対処法について詳しく解説してきました。

でも、実際にアニメーションを実装する際には、どのようなユースケースがあるのでしょうか?

そこで、ここでは、requestAnimationFrameの応用例を4つ紹介します。実際のWebサイトやアプリケーションでよく見られる事例を通して、より実践的なテクニックを学びましょう。

○パララックススクロール

パララックススクロールは、スクロールに合わせて複数の層を異なる速度で動かすことで、奥行きのある効果を生み出すテクニックです。

Webサイトのヒーロー部分やストーリーテリングのセクションなどでよく使われます。

requestAnimationFrameを使って、スクロール位置に応じた要素の位置を計算し、滑らかにアニメーションさせることができます。

const parallaxElements = document.querySelectorAll('.parallax');

function animateParallax() {
  parallaxElements.forEach(element => {
    const speed = element.dataset.speed;
    const yPosition = window.pageYOffset * speed;
    element.style.transform = `translate3d(0, ${yPosition}px, 0)`;
  });

  requestAnimationFrame(animateParallax);
}

requestAnimationFrame(animateParallax);

このコードでは、.parallaxクラスを持つ要素を取得し、それぞれの要素に設定されたdata-speed属性の値に基づいて、スクロール位置に応じた移動距離を計算しています。

そして、translateを使って要素を移動させることで、パララックス効果を実現しています。

実行すると、スクロールに合わせて要素が異なる速度で動き、奥行きのある表現が可能になります。

レイヤー構造を工夫することで、より印象的なデザインを作り出せるでしょう。

○インタラクティブな効果

Webサイトやアプリケーションに、インタラクティブな要素を取り入れることで、ユーザーエンゲージメントを高めることができます。

マウスの動きに反応するアニメーションや、スクロールに連動したエフェクトなどがその一例です。

requestAnimationFrameを使えば、これらのインタラクティブな効果をスムーズに実装できます。

const interactiveElement = document.querySelector('.interactive');

function animateInteractive(event) {
  const mouseX = event.clientX;
  const mouseY = event.clientY;

  interactiveElement.style.transform = `translate3d(${mouseX / 10}px, ${mouseY / 10}px, 0)`;

  requestAnimationFrame(() => {
    animateInteractive(event);
  });
}

document.addEventListener('mousemove', animateInteractive);

このコードでは、マウスの動きに反応して要素を移動させるインタラクティブな効果を実装しています。

mousemoveイベントを監視し、マウスの座標を取得します。

そして、その座標に基づいて要素の位置を計算し、translate3dを使って移動させています。

requestAnimationFrameを使うことで、マウスの動きに合わせて滑らかにアニメーションさせることができます。

○ゲームループ

ゲーム開発においては、ゲームループと呼ばれる一連の処理を繰り返し実行することが基本となります。

ゲームの状態を更新し、描画するこの一連の流れを、requestAnimationFrameを使って制御することができます。

function gameLoop(timestamp) {
  // ゲームの状態を更新
  update();

  // 描画処理を実行
  render();

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

このコードは、シンプルなゲームループの実装例です。

gameLoop関数内で、ゲームの状態を更新するupdate関数と、描画処理を行うrender関数を呼び出しています。

そして、requestAnimationFrameを使って次のフレームでgameLoopを呼び出すことで、ループを継続します。

実際のゲーム開発では、この基本的な構造の上に、ゲームオブジェクトの管理や衝突判定、物理演算などの様々な処理が追加されます。

それぞれの処理を適切に設計し、パフォーマンスを最適化することが重要です。

○パフォーマンスモニタリング

最後に、requestAnimationFrameを使ったパフォーマンスモニタリングの方法を紹介します。

フレームレートやレンダリング時間を計測することで、アプリケーションのパフォーマンスをリアルタイムに可視化できます。

let lastTime = performance.now();
let frameCount = 0;

function monitor(time) {
  frameCount++;

  if (time - lastTime > 1000) {
    const fps = frameCount;
    const renderTime = (time - lastTime) / frameCount;

    console.log(`FPS: ${fps}`);
    console.log(`Render Time: ${renderTime.toFixed(2)}ms`);

    frameCount = 0;
    lastTime = time;
  }

  requestAnimationFrame(monitor);
}

requestAnimationFrame(monitor);

このコードでは、1秒ごとにフレームレートとレンダリング時間を計算し、コンソールに出力しています。

frameCount変数でフレーム数をカウントし、performance.nowを使って経過時間を計測します。

まとめ

さて、ここまでrequestAnimationFrameを使ったFPS制御の方法について、たくさんのサンプルコードを交えながら解説してきましたね。

requestAnimationFrameがブラウザの描画サイクルに同期し、60fpsのアニメーションを実現できることを理解していただけたかと思います。

これを理解し、自分のプロジェクトに応用することで、よりパフォーマンスの高いアニメーションを実現できるはずです。

でも、忘れないでほしいのは、最適化はゴールではなく、手段だということです。

大切なのは、ユーザーに価値を提供し、素晴らしい体験を届けること。

そのために、パフォーマンスという観点からアニメーションを最適化する。そんな姿勢でプロジェクトに取り組んでいただければと思います。