React アニメーションの基本と requestAnimationFrame を活用する方法

Reactでアニメーションを実装するとき、requestAnimationFrame を活用することで、スムーズで効率的なアニメーションが実現できます。本記事では、requestAnimationFrame を使った再帰的アニメーション処理を実装し、その動作原理や効果的な制御方法について解説します。

1. requestAnimationFrame の概要

requestAnimationFrame は、ブラウザが描画するフレームに合わせて関数を実行するためのAPIです。これにより、アニメーションがスムーズに動き、無駄なリソースを消費することなく効率的に動作します。

主な特徴:

  • 60FPS(1秒間に最大60回)で実行される。
  • ブラウザのリフレッシュレートに合わせて描画されるため、カクつきが少なく、スムーズなアニメーションが可能。
  • バックグラウンドタブでは自動的に停止し、リソースを節約。

2. requestAnimationFrame を使ったアニメーションの実装

Reactの useEffect を使って、requestAnimationFrame を実装し、アニメーションをループさせる基本的な方法を見ていきましょう。以下は、物体の位置を更新しながらスムーズにアニメーションを実行するサンプルコードです。

import React, { useState, useEffect } from 'react';

const MovingObject = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [velocity, setVelocity] = useState({ x: 1, y: 1 });
  const [inertia, setInertia] = useState(true);

  useEffect(() => {
    if (!inertia) return;

    const friction = 0.95;
    let currentVelocity = { ...velocity };
    let animationFrameId: number;

    const animate = () => {
      setPosition((prev) => ({
        x: prev.x + currentVelocity.x,
        y: prev.y + currentVelocity.y,
      }));

      currentVelocity = {
        x: currentVelocity.x * friction,
        y: currentVelocity.y * friction,
      };

      if (
        Math.abs(currentVelocity.x) < 0.1 &&
        Math.abs(currentVelocity.y) < 0.1
      ) {
        setInertia(false);
        return;
      }

      animationFrameId = requestAnimationFrame(animate);
    };

    animate();

    return () => cancelAnimationFrame(animationFrameId);
  }, [inertia, velocity]);

  return (
    <div style={{ position: 'absolute', left: position.x, top: position.y }}>
      {/* ここに動かすオブジェクトのコンテンツを追加 */}
      <div>動いています!</div>
    </div>
  );
};

export default MovingObject;

3. requestAnimationFrame再帰的呼び出し

上記のコードでは、requestAnimationFrame(animate) を使って、animate**** 関数が次のフレームで呼び出される ようにしています。これを繰り返すことで、アニメーションが継続的に実行されます。

const animate = () => {
  setPosition((prev) => ({
    x: prev.x + currentVelocity.x,
    y: prev.y + currentVelocity.y,
  }));

  // 速度を減速
  currentVelocity = {
    x: currentVelocity.x * friction,
    y: currentVelocity.y * friction,
  };

  // 速度がほぼゼロになったらアニメーションを停止
  if (Math.abs(currentVelocity.x) < 0.1 && Math.abs(currentVelocity.y) < 0.1) {
    setInertia(false);
    return;
  }

  // 次のフレームをリクエスト
  animationFrameId = requestAnimationFrame(animate);
};

このように、requestAnimationFrame再帰的に呼ぶことで、1フレームごとにオブジェクトの位置や状態を更新し、アニメーションを滑らかに動かすことができます。

4. cancelAnimationFrame でアニメーションを制御

アニメーションの停止には、requestAnimationFrame で得られた ID を使って cancelAnimationFrame を呼びます。このIDを利用して、アニメーションを停止させることができます。

return () => cancelAnimationFrame(animationFrameId);

useEffect 内で返すクリーンアップ関数により、コンポーネントがアンマウントされる際やアニメーションが終了する際に、アニメーションを止めることができます。

5. 再帰呼び出しとパフォーマンスの最適化

requestAnimationFrame を使って再帰的に関数を呼び出すと、アニメーションは高頻度で更新されますが、ブラウザのリフレッシュレートに合わせて制御されるため、過剰に処理が呼ばれることはありません。これにより、アニメーションがスムーズに動作し、パフォーマンスに悪影響を与えません。

さらに、アニメーションが必要ないときは、inertia のような状態を用いてアニメーションを停止することができます。

if (Math.abs(currentVelocity.x) < 0.1 && Math.abs(currentVelocity.y) < 0.1) {
  setInertia(false); // 速度が低くなったらアニメーションを停止
  return;
}

6. 終わりに

requestAnimationFrame を使用すると、効率的でスムーズなアニメーション を実現できます。ブラウザの描画タイミングに合わせてアニメーションを実行するため、カクつきが少なく、リソースの無駄使いを防ぐことができます。\ アニメーションのフレームレートを調整する方法や、cancelAnimationFrame でアニメーションを停止する方法も紹介しました。

これらをうまく組み合わせることで、Reactでのアニメーション処理がより効果的に、そしてパフォーマンスを維持しながら実装できるようになります。