サイコロゲームで比較!React Three Fiber vs Framer Motion

サイコロゲームで比較!React Three Fiber vs Framer Motion

作成日:
更新日:

ブラウザで動くサイコロゲームを2つの異なるアプローチで実装してみました。1〜3個のサイコロを選んで振ることができます。

2つのアプローチ

アプローチライブラリ特徴
3D版React Three Fiber + drei本格的な3Dレンダリング、WebGL
2D版Framer Motion + CSSCSS 3D transformで疑似3D、軽量

どちらも同じ機能を持ちますが、実装方法と見た目、パフォーマンス特性が異なります。


React Three Fiber版(本格3D)

WebGLを使った本格的な3Dサイコロです。カメラをドラッグで回転、スクロールでズームできます。

3Dシーンを読み込み中...

特徴

  • リアルな3D表現: RoundedBoxで角丸のサイコロを表現
  • 物理的な質感: PBRマテリアルと環境マッピング
  • インタラクティブ: カメラ操作(回転・ズーム)
  • 影と光: ContactShadowsでリアルな接地影

Framer Motion版(CSS疑似3D)

CSSのtransform-style: preserve-3dを使った疑似3Dサイコロです。軽量でシンプルな実装です。

読み込み中...

特徴

  • 軽量: WebGLを使わないため、低負荷
  • シンプル: CSSとFramer Motionのみで実装
  • スムーズ: Framer Motionのspringアニメーション
  • 互換性: WebGL非対応環境でも動作

比較表

項目React Three FiberFramer Motion
見た目本格的な3D疑似3D(平面的)
パフォーマンスやや重い(WebGL)軽量(CSS)
実装難易度中〜高低〜中
カメラ操作可能不可
ブラウザ互換性WebGL必要ほぼ全て
ファイルサイズ大きい(Three.js)小さい
用途本格的な3D演出軽い演出・UI

実装解説

共通:サイコロロジック(useDice.ts)

両方のバージョンで共通のカスタムフックを使用しています。

useDice.ts
export function useDice(initialCount: number = 1) {
  const [count, setCount] = useState(initialCount);
  const [values, setValues] = useState<number[]>([]);
  const [isRolling, setIsRolling] = useState(false);
 
  const roll = useCallback(() => {
    setIsRolling(true);
    
    // アニメーション中に値を何度か変更(演出)
    const interval = setInterval(() => {
      const tempValues = Array.from({ length: count }, () => 
        Math.floor(Math.random() * 6) + 1
      );
      setValues(tempValues);
    }, 100);
 
    // 1秒後に確定
    setTimeout(() => {
      clearInterval(interval);
      const finalValues = Array.from({ length: count }, () => 
        Math.floor(Math.random() * 6) + 1
      );
      setValues(finalValues);
      setIsRolling(false);
    }, 1000);
  }, [count]);
 
  return { values, isRolling, roll, count, setCount };
}

React Three Fiber版のポイント

RoundedBoxで角丸サイコロ

dreiのRoundedBoxを使うと、簡単に角丸の立方体を作成できます。

Dice3D.tsx
import { RoundedBox } from '@react-three/drei';
 
<RoundedBox args={[1, 1, 1]} radius={0.08} smoothness={4}>
  <meshStandardMaterial color="#f5f5f5" roughness={0.3} metalness={0.1} />
</RoundedBox>

useFrameで回転アニメーション

useFrameフックで毎フレームの更新処理を行います。

Dice3D.tsx
import { useFrame } from '@react-three/fiber';
 
useFrame((_, delta) => {
  if (isRolling) {
    // ロール中:高速回転
    groupRef.current.rotation.x += rollSpeed.current.x * delta;
    groupRef.current.rotation.y += rollSpeed.current.y * delta;
    
    // 徐々に減速
    rollSpeed.current.x *= 0.98;
    rollSpeed.current.y *= 0.98;
  } else {
    // ロール終了:目標の向きにスムーズに回転
    groupRef.current.rotation.x = THREE.MathUtils.lerp(
      groupRef.current.rotation.x,
      targetRotation.current.x,
      delta * 5
    );
  }
});

各面にドットを配置

サイコロの各面(1〜6)にドット(球体)を配置します。

Dice3D.tsx
// 各面のドット位置を計算
const getDotPositions = (face: string, value: number) => {
  const offset = 0.51; // 面からの距離
  const spread = 0.25; // ドット間の距離
  
  // 値に応じたパターンを返す
  // ...
};
 
// ドットを配置
{dots.map((pos, i) => (
  <mesh key={i} position={pos}>
    <sphereGeometry args={[0.08, 16, 16]} />
    <meshStandardMaterial color="#1a1a1a" />
  </mesh>
))}

Framer Motion版のポイント

CSS 3D Transformで疑似3D

transform-style: preserve-3dとFramer MotionのrotateX/Y/Zを組み合わせます。

Dice2D.tsx
<motion.div
  style={{
    transformStyle: 'preserve-3d',
    perspective: 1000,
  }}
  animate={
    isRolling
      ? {
          rotateX: [0, 360, 720, 1080],
          rotateY: [0, 180, 360, 540],
          rotateZ: [0, 90, 180, 270],
        }
      : { rotateX: 0, rotateY: 0, rotateZ: 0 }
  }
  transition={{
    duration: 1,
    ease: 'easeOut',
  }}
>
  {/* サイコロの目 */}
</motion.div>

ドットのアニメーション

値が変わるたびにドットがポップインするアニメーションを追加。

Dice2D.tsx
{dots.map((dot, index) => (
  <motion.div
    key={index}
    initial={{ scale: 0 }}
    animate={{ scale: 1 }}
    transition={{
      delay: index * 0.05,
      type: 'spring',
      stiffness: 400,
    }}
    style={{
      position: 'absolute',
      left: `${dot.x}%`,
      top: `${dot.y}%`,
      borderRadius: '50%',
      background: '#1a1a1a',
    }}
  />
))}

使い分けのガイドライン

React Three Fiberを選ぶべき場合

  • 本格的な3D演出が必要な場合
  • カメラ操作物理演算を使いたい場合
  • 環境マッピングリアルな影が必要な場合
  • 3Dモデルのインポートが必要な場合

Framer Motionを選ぶべき場合

  • 軽量な演出で十分な場合
  • パフォーマンス優先の場合
  • WebGL非対応環境も考慮する場合
  • シンプルな実装を求める場合
  • UIのマイクロインタラクションとして使う場合

まとめ

同じ「サイコロを振る」機能でも、実装方法によって見た目や特性が大きく異なります。

観点R3FFramer Motion
リッチな3D体験
軽量・シンプル
学習コストやや高い低い

プロジェクトの要件に応じて、適切なアプローチを選択しましょう。

ポイント:

  • 両方ともReactのコンポーネントモデルを活かせる
  • 状態管理は共通のカスタムフックで抽象化可能
  • 見た目の違いは技術選択で決まる

ぜひ両方試して、違いを体感してみてください!


参考リンク