【React】React Three fiber を使ってアニメーションを実装する

React

webでアニメーションを実装したい場合の一つの手段として、今回はReact Three fiberを使ってReactでThree.jsのように3Dアニメを実装する方法をメモしておきます。(※環境構築は解説していません)

今回はアニメーションさせるところを重点的に話していますが、基礎的なオブジェクト、ライトの追加などシーンの構築は以下の記事で紹介しています。

React Three fiberとは?

React Three Fiberは、Reactアプリケーションの中で3Dグラフィックスを簡単に扱えるようにするライブラリです。Three.jsというJavaScriptの3DライブラリをReactのスタイルで使えるようにラップしており、Reactコンポーネントとして3Dオブジェクトやシーンを操作できます。

これを使うと、HTMLやCSSの代わりにReactのコンポーネントとして3D要素を作成でき、Reactの状態管理やライフサイクルを活用しながら、動的でインタラクティブな3Dコンテンツを作ることができます。

つまり、Reactで3Dの世界を簡単に構築でき、3Dグラフィックスの学習コストを下げ、Reactアプリの中で3D要素を直感的に統合できるようになります。

基本的なセットアップ

import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";

const Cube = ({ position, size, color }) => {
  const ref = useRef();
  useFrame((state, delta) => {
    //アニメーションの内容
    ref.current.rotation.y += delta * 2.0;
    ref.current.rotation.x += delta;
    ref.current.position.z = Math.sin(state.clock.elapsedTime) * 2;
  });
  console.log(ref);
  return (
    <mesh position={position} ref={ref}>
      <boxGeometry args={size} />
      <meshStandardMaterial color={color} />
    </mesh>
  );
};

このコードは、React Three Fiberを使って3DオブジェクトCube(立方体)を描画し、そのオブジェクトにアニメーションを加える基本的なセットアップです。以下のポイントを説明します。

  1. Canvasコンポーネント: Canvasは、3Dシーンを描画するためのキャンバスを提供します。このコンポーネント内に3Dオブジェクトを配置していきます。
  2. Cubeコンポーネント: Cubeは、3Dの立方体を描画するコンポーネントです。position(位置)、size(サイズ)、color(色)をプロパティとして受け取ります。
  3. useRef: useRefは、Cubeコンポーネント内のメッシュ(立方体)の参照を保持するために使います。これにより、後でそのオブジェクトにアクセスし、状態を変更できます。
  4. useFrame: useFrameは、毎フレーム実行される関数を定義するフックです。ここでは、立方体の回転や移動のアニメーションを実行しています。ref.current.rotationで立方体の回転を変更し、ref.current.position.zで立方体のz軸の位置を変化させています。Math.sinを使って時間の経過に応じて上下に動くアニメーションを作っています。
  5. meshコンポーネント: 3Dオブジェクト(ここでは立方体)を表示するためにmeshコンポーネントを使用します。positionプロパティで位置を指定し、refuseRefで作った参照を設定します。
  6. boxGeometryとmeshStandardMaterial: boxGeometryは立方体の形状を作り、meshStandardMaterialはその立方体に色をつけます。

これにより、Reactで簡単に3Dオブジェクトを作り、その動きをアニメーションで制御することができます。

アニメーションさせる場合、ReactのuseRefとr3fのuseFrameというhookを使います。以下、これらのhookについて使い方や見方を書いています。

useRefで参照されるもの

const ref = useRef();

<mesh position={position} ref={ref}>
      <boxGeometry args={size} />
      <meshStandardMaterial color={color} />
    </mesh>

例えば上記のようにmeshの情報を見るとcurrentの下にpositionやrotationなどいろいろな情報が入ってます

useRefはレンダリングサイクルに依存しないため、毎フレーム状態を更新する必要があるオブジェクトに適しています。useStateと違って、再レンダリングを引き起こさないのでパフォーマンスが向上します。

useFrameでやっていること

useFrame((state, delta) => {
  meshRef.current.rotation.x += delta;  // deltaを使って回転を変化させる
});

useFrameはThree.jsのレンダリングループと連動しており、1秒あたり60回以上(フレームレートに依存)呼び出されます。deltaは前のフレームからの経過時間で、フレーム間のスムーズなアニメーションを実現するために使用します。

useFrameのstate

useFramestateは、アニメーションやシーンの制御に役立つさまざまな情報を提供するオブジェクトです。Three.jsのレンダリングループに連動しており、1秒間に複数回呼び出されるため、リアルタイムでのアニメーション制御に適しています。

stateの主なプロパティ

clock
  • 時間の管理を行います。clockを使うと、アニメーションを開始してからどれくらいの時間が経過したかなどを取得できます。
const elapsedTime = state.clock.getElapsedTime(); meshRef.current.rotation.x = elapsedTime; // 経過時間を使って回転させる
camera

シーン内のカメラにアクセスできます。カメラの位置や向きを変更することで、視点をリアルタイムで変更することが可能です。

state.camera.position.x = Math.sin(state.clock.getElapsedTime()) * 5; state.camera.lookAt(meshRef.current.position);
gl

WebGLRendererオブジェクト(Three.jsのレンダラー)にアクセスでき、より細かいWebGLの制御が可能です。例えば、レンダリングの設定を変更したり、カスタムレンダーパスを追加したりできます。

state.gl.setClearColor(0x000000); // 背景色を黒に設定
scene

現在のシーンのオブジェクト。scene.addscene.removeでオブジェクトを動的に追加・削除することが可能です。

if (someCondition) { state.scene.add(new THREE.AxesHelper(5)); // シーンに座標軸を追加 }
size

ウィンドウのサイズを示すオブジェクトで、widthheightが含まれます。ウィンドウサイズに基づいて、レンダリングを調整したり、オブジェクトのスケーリングを調整する際に使用されます。

meshRef.current.scale.set(state.size.width / 100, state.size.height / 100, 1);
mouse

マウスの位置を取得できます。-1から1の範囲で正規化されており、インタラクティブなエフェクトやアニメーションを作成する際に役立ちます

meshRef.current.position.x = state.mouse.x * 5; // マウスのX位置に応じて移動
viewport

カメラに対するビューポートの情報が含まれます。widthheightがあり、オブジェクトをビューポートに合わせたスケーリングや配置を行う際に使えます。

const aspectRatio = state.viewport.aspect; // ビューポートのアスペクト比

インタラクション

クリックやホバーなどのインタラクションはuseStateで状態を管理し、その状態を元にプロパティを変更したり、useFrame内のアニメーションを変更することができます。

このコードは、React Three Fiberを使って3Dオブジェクト(ここでは球体)にインタラクションを加え、マウスのホバー(マウスがオブジェクトに触れる)によってアニメーションや見た目を変更する方法を示しています。以下に各部分を詳しく説明します。

const Sphere = ({ position, size, color }) => {
  const ref = useRef();
  const [isHovered, setIsHovered] = useState(false);
  useFrame((state, delta) => {
    const speed = isHovered ? 1 : 0.2;
    ref.current.rotation.y += delta * speed;
  });
  return (
    <mesh
      position={position}
      ref={ref}
      onPointerEnter={(e) => (e.stopPropagation(), setIsHovered(true))}
      onPointerLeave={(e) => setIsHovered(false)}
    >
      <sphereGeometry args={size} />
      <meshStandardMaterial color={isHovered ? "yellow" : "blue"} wireframe />
    </mesh>
  );
};

Reactは自前のイベントシステムを持っており、イベントハンドラ(onClick, onMouseEnterなどをコンポーネント内で直接定義することで、状態管理やライフサイクルの管理が容易になります。addEventListenerを使うと、Reactのライフサイクルから外れ、予期しない動作を引き起こす可能性があります。

1. useStateとuseRefの使用

  • useState: このフックは、コンポーネント内で状態を管理するために使います。ここでは、球体が「ホバーされているかどうか」を管理するisHoveredという状態を作成しています。初期値はfalseです。
  • useRef: useRefは、3Dオブジェクト(球体)への参照を保持するために使われます。これにより、球体の回転を制御することができます。

2. useFrameによるアニメーション制御

  • useFrame: このフックは、毎フレーム実行される関数を定義します。ここでは、isHoveredの状態に応じて回転のスピードを変更しています。isHoveredtrueの場合、回転が速く(スピード1)、falseの場合は遅く(スピード0.2)なります。
  • ref.current.rotation.y: 球体の回転をY軸方向に設定しています。deltaは前回のフレームからの経過時間で、これを使って滑らかな回転を実現しています。

3. インタラクションの追加

  • onPointerEnteronPointerLeave: これらは、マウスが球体に触れたとき(onPointerEnter)と、マウスが球体から離れたとき(onPointerLeave)のイベントハンドラです。これらのイベントでisHovered状態を変更し、ホバー状態に応じて球体の色や回転スピードを変えています。
    • onPointerEnter: setIsHovered(true)isHoveredtrueに設定し、球体にホバーしたことを通知します。
    • onPointerLeave: setIsHovered(false)isHoveredfalseに設定し、ホバーが解除されたことを通知します。

4. 色の変更

  • meshStandardMaterial: 球体の色は、isHoveredの状態に応じて変わります。ホバーされていない場合は青色("blue")、ホバーされている場合は黄色("yellow")になります。wireframeオプションが設定されているため、オブジェクトの線画として表示されます。

5. Reactのイベントシステム

Reactは独自のイベントシステムを提供しており、onPointerEnteronPointerLeaveのようなイベントハンドラをコンポーネント内で直接定義できます。これにより、状態管理やライフサイクルをReactの仕組みと統合しやすくなり、コードが直感的で予測可能になります。もしaddEventListenerを直接使うと、Reactのライフサイクルから外れるため、予期しない挙動が発生する可能性があります。

まとめ

React Three Fiberを使った簡単なセットアップと基礎的なインタラクションのコードを紹介しました。

React Three Fiberを使うことで、Reactの中で簡単に3Dアニメーションを実装でき、インタラクションを追加するのもスムーズです。useRefuseFrameuseStateなどのReactフックを活用し、3Dシーン内でのオブジェクト操作を直感的に実現できるため、インタラクティブなウェブアプリケーションやゲームの開発に非常に有用です。

もうReactに完全に慣れてしまった、という方にはかなり書きやすくなっていると思います。Three.jsで3Dアニメーションに挫折してしまったという方にもオススメです。