【超簡単】React Three Fiber x Drei x TypeScript入門!標準オブジェクトで作るポケポケ風3D背景!

【超簡単】React Three Fiber x Drei x TypeScript入門!標準オブジェクトで作るポケポケ風3D背景!
  • URLをコピーしました!

最近のWebアプリ開発では、3Dグラフィックス を活用するケースが増えてきました。特に、React Three Fiber(R3F) を使えば、React のコンポーネントとして手軽に Three.js を扱うことができます。

今回は、Three.js のラッパーライブラリ Drei も活用しながら、「ポケポケ風」のカラフルな3D背景React + TypeScript で実装していきます!

📌 しかも今回は、特別な3Dモデルは一切使いません!
Three.js に標準で用意されている基本オブジェクト(Box, Plane など)だけ で、ここまで表現できる!というのを体験してもらいます。

💡 完成イメージ
👇作成するのは、こんな感じの 3D 背景 です👇

📺 YouTubeでデモを見る:こちらのリンクから視聴できます。

カラフルなカードが円形に配置された 3D ワールド
鏡面反射を活用したおしゃれな背景
HTML/CSS を組み合わせた UI

🚀 「特別な3Dモデルは不要!標準オブジェクトだけでここまでできる!」 という点に注目して進めていきましょう!

それでは、実際にコードを書いてみましょう!

TypeScript x React Three Fiberのレッスンや作品は、今後もどんどん作成していきます!
YouTubeで告知致しますので、ぜひYouTubeのチャンネル登録をして通知をお待ちください!

📺 YouTubeを見る:こちらのリンクから視聴できます。

React Three Fiberで何ができるのか知りたい方は、下記を参考にしてみてください!
簡単にできる作品を用意しております!

目次

🚀技術要素の紹介:このプロジェクトで使うツールとライブラリ

利用するツールやライブラリは、ご自身の使いやすいものに変更しても良いですが、このプロジェクトではこちらを前提に説明をしていきます。

VSCode
  • Microsoftが提供する無料のコードエディタです。
  • VSCodeでなくても良いですが、多くの拡張機能がありますので、おすすめです。
  • ESLintやPrettierを入れるのもおすすめです。
Node.js
  • ChromeのV8 JavaScriptエンジン上に構築されたJavaScriptランタイムです。
  • ブラウザの外でJavaScriptコードを実行することができます。
  • こちらは既にインストールされている前提で説明しますので、「https://nodejs.org/ja」からダウンロードしておいてください。
    ※長期安定版であるLTSバージョンをダウンロードすることをお勧めします。
Vite
  • モダンなウェブプロジェクトのためのビルドツールです。 速く軽量という特徴があります。
  • 以前まで使われていた「CRA(create-react-app)」は、公式HPでも記載がなく、古い技術になってしまいました。
  • これからは、Reactでアプリを作る際は、Viteが最良の選択肢となるはずです。
React
  • UI(ユーザーインターフェース)を構築するためのJavaScriptライブラリです。Facebookが開発し、現在も多くのウェブアプリで使われています。
Three.js
  • 3Dグラフィックスを簡単に作成するためのJavaScriptライブラリです。WebGLの複雑な操作を抽象化し、直感的な3D開発を可能にします。
  • 3Dグラフィックスを簡単に作成でき、WebGLの直接操作よりも扱いやすいです。
React Three Fiber
  • Three.jsをReactで扱えるようにするためのライブラリです。Reactのコンポーネント構造とThree.jsの3Dエンジンを融合します。
  • Reactの開発スタイルでThree.jsを扱えるため、直感的かつ効率的な開発が可能になります。
React Three Drei
  • React Three Fiberのための便利なユーティリティコンポーネント集です。よく使われるThree.jsの機能を簡単に追加できます。
  • 複雑なThree.jsの機能を短いコードで実現できるため、学習コストを削減できます。

🚀 React Three Fiber × Drei × TypeScript で「ポケポケ風3D背景」を作ろう!

📌環境構築は、こちらの記事で紹介してますので、まだの方は確認してみてください。

📌今回は、特殊な3Dモデルは利用せずに、React Three FiberやReact Three Dreiで利用可能な標準オブジェクトのみを利用します。
標準オブジェクトを詳しく知りたい方は、こちらの記事も参考にしてみてください。

今回、作成したソースコード全量は、GitHubで確認してください。

💾 GitHubリポジトリ:こちらのリンクでソースコードをチェック!

💡ログインフォームを作る!!

まずは、シンプルにReactでログインフォームを作りましょう。
CSSライブラリとして、tailwind-cssを利用しています。

// === メインアプリコンポーネント ===
const LoginScreen = () => {
  return (
    <>
      {/* ログインフォーム */}
      <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white/90 rounded-lg p-8 shadow-lg text-center">
        <h2 className="text-2xl font-semibold text-gray-700 mb-6">ログイン</h2>
        <form>
          <div className="mb-4">
            <input
              type="text"
              placeholder="ユーザー名"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              placeholder="パスワード"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
          >
            ログイン
          </button>
        </form>
      </div>
    </>
  );
};

export default LoginScreen;

💡Canvasを背景に表示!!

最初にCanvasを配置して、カメラやライティングを用意しておきましょう。

import { Canvas } from "@react-three/fiber";
import {
  OrbitControls,
} from "@react-three/drei";

// === メインアプリコンポーネント ===
const LoginScreen = () => {
  return (
    <>
      {/* Three.jsのCanvas */}
      <Canvas camera={{ position: [0, 0, 10], fov: 50 }}>
        <color attach="background" args={["white"]} />
        {/* ライティング */}
        <ambientLight intensity={10} />
        <directionalLight position={[0, 5, 0]} intensity={15} />

        {/* カメラ操作 */}
        <OrbitControls
          enablePan={false}
          enableZoom={false}
          minPolarAngle={Math.PI / 2}
          maxPolarAngle={Math.PI / 2}
        />
      </Canvas>

      {/* HTML/CSSで作成したログインフォーム */}
      <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white/90 rounded-lg p-8 shadow-lg text-center">
        <h2 className="text-2xl font-semibold text-gray-700 mb-6">ログイン</h2>
        <form>
          <div className="mb-4">
            <input
              type="text"
              placeholder="ユーザー名"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              placeholder="パスワード"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
          >
            ログイン
          </button>
        </form>
      </div>
    </>
  );
};

export default LoginScreen;

💡カードパックオブジェクトの配置!!

次に背景にポケポケのカードパック風のオブジェクトを配置します。
これだけでも、何となくポケポケのパックを選ぶ画面に見えませんか?(笑)

import { Canvas } from "@react-three/fiber";
import {
  MeshReflectorMaterial,
  OrbitControls,
} from "@react-three/drei";


// === [0,0,0]を中心にカードのパックをイメージしたboxを円形に配置する ===
const CircularCardPack = ({
  radius = 5,
  count = 10,
  cardSize = [1, 1.5, 0.1], // 幅, 高さ, 奥行き
}: {
  radius?: number; // 円の半径
  count?: number; // 配置するカードの数
  cardSize?: [number, number, number]; // カードのサイズ
}) => {
  const cards = Array.from({ length: count }); // 配列を生成
  return (
    <group>
      {cards.map((_, i) => {
        const angle = (i / count) * Math.PI * 2; // 円周上の角度
        const x = radius * Math.cos(angle); // X座標
        const z = radius * Math.sin(angle); // Z座標
        const rotationY = -angle + Math.PI / 2; // カードが中心を向くように回転

        return (
          <mesh
            key={i}
            position={[x, -1, z]} // カードの位置
            rotation={[0, rotationY, 0]} // カードの向き
          >
            <boxGeometry args={cardSize} />
            <meshStandardMaterial
              color={`hsl(${(i / count) * 360}, 70%, 50%)`}
            />
          </mesh>
        );
      })}
    </group>
  );
};

// === メインアプリコンポーネント ===
const LoginScreen = () => {
  return (
    <>
      {/* Three.jsのCanvas */}
      <Canvas camera={{ position: [0, 0, 10], fov: 50 }}>
        <color attach="background" args={["white"]} />
        {/* ライティング */}
        <ambientLight intensity={10} />
        <directionalLight position={[0, 5, 0]} intensity={15} />

        {/* カメラ操作 */}
        <OrbitControls
          enablePan={false}
          enableZoom={false}
          minPolarAngle={Math.PI / 2}
          maxPolarAngle={Math.PI / 2}
        />

        {/* カードオブジェクト */}
        <CircularCardPack />
      </Canvas>

      {/* HTML/CSSで作成したログインフォーム */}
      <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white/90 rounded-lg p-8 shadow-lg text-center">
        <h2 className="text-2xl font-semibold text-gray-700 mb-6">ログイン</h2>
        <form>
          <div className="mb-4">
            <input
              type="text"
              placeholder="ユーザー名"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              placeholder="パスワード"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
          >
            ログイン
          </button>
        </form>
      </div>
    </>
  );
};

export default LoginScreen;

💡パックの下に鏡面を配置!!

ポケポケのパックを選ぶところを確認してみると、パックが反射して見えるので、同じようにパックの下に鏡面を追加してみます。

import { Canvas } from "@react-three/fiber";
import {
  MeshReflectorMaterial,
  OrbitControls,
} from "@react-three/drei";

// === 鏡面コンポーネント ===
const ReflectivePlane = () => {
  return (
    <>
      <mesh rotation-x={-Math.PI / 2} position={[0, -2, 0]}>
        <planeGeometry args={[100, 100]} /> {/* 鏡を大きくして背景全体を覆う */}
        <MeshReflectorMaterial
          blur={[200, 100]}
          mixBlur={0.7}
          mixStrength={1}
          mixContrast={1}
          resolution={1024}
          mirror={1}
          depthScale={0.1}
          reflectorOffset={0.2}
        />
      </mesh>
    </>
  );
};

// === [0,0,0]を中心にカードのパックをイメージしたboxを円形に配置する ===
const CircularCardPack = ({
  radius = 5,
  count = 10,
  cardSize = [1, 1.5, 0.1], // 幅, 高さ, 奥行き
}: {
  radius?: number; // 円の半径
  count?: number; // 配置するカードの数
  cardSize?: [number, number, number]; // カードのサイズ
}) => {
  const cards = Array.from({ length: count }); // 配列を生成
  return (
    <group>
      {cards.map((_, i) => {
        const angle = (i / count) * Math.PI * 2; // 円周上の角度
        const x = radius * Math.cos(angle); // X座標
        const z = radius * Math.sin(angle); // Z座標
        const rotationY = -angle + Math.PI / 2; // カードが中心を向くように回転

        return (
          <mesh
            key={i}
            position={[x, -1, z]} // カードの位置
            rotation={[0, rotationY, 0]} // カードの向き
          >
            <boxGeometry args={cardSize} />
            <meshStandardMaterial
              color={`hsl(${(i / count) * 360}, 70%, 50%)`}
            />
          </mesh>
        );
      })}
    </group>
  );
};

// === メインアプリコンポーネント ===
const LoginScreen = () => {
  return (
    <>
      {/* Three.jsのCanvas */}
      <Canvas camera={{ position: [0, 0, 10], fov: 50 }}>
        <color attach="background" args={["white"]} />
        {/* ライティング */}
        <ambientLight intensity={10} />
        <directionalLight position={[0, 5, 0]} intensity={15} />

        {/* カメラ操作 */}
        <OrbitControls
          enablePan={false}
          enableZoom={false}
          minPolarAngle={Math.PI / 2}
          maxPolarAngle={Math.PI / 2}
        />

        {/* カードパックオブジェクト */}
        <CircularCardPack />

        {/* 鏡面オブジェクト */}
        <ReflectivePlane />
      </Canvas>

      {/* HTML/CSSで作成したログインフォーム */}
      <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white/90 rounded-lg p-8 shadow-lg text-center">
        <h2 className="text-2xl font-semibold text-gray-700 mb-6">ログイン</h2>
        <form>
          <div className="mb-4">
            <input
              type="text"
              placeholder="ユーザー名"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              placeholder="パスワード"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
          >
            ログイン
          </button>
        </form>
      </div>
    </>
  );
};

export default LoginScreen;

💡背景を追加!!

これは無くても良いのですが、ちょっと寂しかったので、背景も追加してみます。
背景もReact Three Dreiの標準のものを利用します。

import { Canvas } from "@react-three/fiber";
import {
  Environment,
  MeshReflectorMaterial,
  OrbitControls,
} from "@react-three/drei";

// === 鏡面コンポーネント ===
const ReflectivePlane = () => {
  return (
    <>
      <mesh rotation-x={-Math.PI / 2} position={[0, -2, 0]}>
        <planeGeometry args={[100, 100]} /> {/* 鏡を大きくして背景全体を覆う */}
        <MeshReflectorMaterial
          blur={[200, 100]}
          mixBlur={0.7}
          mixStrength={1}
          mixContrast={1}
          resolution={1024}
          mirror={1}
          depthScale={0.1}
          reflectorOffset={0.2}
        />
      </mesh>
    </>
  );
};

// === [0,0,0]を中心にカードのパックをイメージしたboxを円形に配置する ===
const CircularCardPack = ({
  radius = 5,
  count = 10,
  cardSize = [1, 1.5, 0.1], // 幅, 高さ, 奥行き
}: {
  radius?: number; // 円の半径
  count?: number; // 配置するカードの数
  cardSize?: [number, number, number]; // カードのサイズ
}) => {
  const cards = Array.from({ length: count }); // 配列を生成
  return (
    <group>
      {cards.map((_, i) => {
        const angle = (i / count) * Math.PI * 2; // 円周上の角度
        const x = radius * Math.cos(angle); // X座標
        const z = radius * Math.sin(angle); // Z座標
        const rotationY = -angle + Math.PI / 2; // カードが中心を向くように回転

        return (
          <mesh
            key={i}
            position={[x, -1, z]} // カードの位置
            rotation={[0, rotationY, 0]} // カードの向き
          >
            <boxGeometry args={cardSize} />
            <meshStandardMaterial
              color={`hsl(${(i / count) * 360}, 70%, 50%)`}
            />
          </mesh>
        );
      })}
    </group>
  );
};

// === メインアプリコンポーネント ===
const LoginScreen = () => {
  return (
    <>
      {/* Three.jsのCanvas */}
      <Canvas camera={{ position: [0, 0, 10], fov: 50 }}>
        <color attach="background" args={["white"]} />
        {/* ライティング */}
        <ambientLight intensity={10} />
        <directionalLight position={[0, 5, 0]} intensity={15} />

        {/* カメラ操作 */}
        <OrbitControls
          enablePan={false}
          enableZoom={false}
          minPolarAngle={Math.PI / 2}
          maxPolarAngle={Math.PI / 2}
        />

        {/* カードパックオブジェクト */}
        <CircularCardPack />

        {/* 鏡面オブジェクト */}
        <ReflectivePlane />

        {/* 背景 */}
        <Environment preset="night" background />
      </Canvas>

      {/* HTML/CSSで作成したログインフォーム */}
      <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white/90 rounded-lg p-8 shadow-lg text-center">
        <h2 className="text-2xl font-semibold text-gray-700 mb-6">ログイン</h2>
        <form>
          <div className="mb-4">
            <input
              type="text"
              placeholder="ユーザー名"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <div className="mb-6">
            <input
              type="password"
              placeholder="パスワード"
              className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition"
          >
            ログイン
          </button>
        </form>
      </div>
    </>
  );
};

export default LoginScreen;

最後に

今回は、「標準オブジェクトで作るポケポケ風3D背景!」ということで、React Three FiberとReact Three Dreiの標準オブジェクトのみで、ポケポケのパック選択の画面を再現してみました!!

📌実際の動きに関しては、下記のYouTubeを見てもらえると詳細に理解できると思います!

📺 YouTubeでデモを見る:こちらのリンクから視聴できます。

📌今回作成したコードは、GitHubに載せていますので、こちらも確認してみてください!

💾 GitHubリポジトリ:こちらのリンクでソースコードをチェック!

📌Meshyを利用して、3Dオブジェクトに置き換えれば、さらに理想に近づけることも可能!!

Meshyは、AIで簡単に3Dオブジェクトを生成できるサービスです。
これを利用することで、簡単に理想の3Dオブジェクトを生成することが可能なので、さらに理想に近づけることができると思います!

📺 Meshyを確認する:こちらのリンクから公式ページで確認できます。

参考になった方は、是非チャンネル登録をお願いします!

TypeScript x React Three Fiberのレッスンや作品は、今後もどんどん作成していきます!
YouTubeで告知致しますので、ぜひYouTubeのチャンネル登録をして通知をお待ちください!

📺 YouTubeを見る:こちらのリンクから視聴できます。

React Three Fiberで何ができるのか知りたい方は、下記を参考にしてみてください!
簡単にできる作品を用意しております!

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

情報セキュリティを勉強するために始めたブログです。
新人のため、広い心を持って見ていただけると嬉しく思います。
楽しくプログラミングを勉強するために、「Teech Lab.」もありますので、ソフトウェア開発にも興味があればぜひ覗いて見てください!

目次