🐥

Reactによる3Dプロフィール画面の作成

に公開

React + Three.js で作る美しい 3D プロフィールサイト

こんにちは!今回は、ReactThree.jsを使って、インタラクティブな 3D プロフィールサイトを作成する方法をご紹介します。

🎯 完成イメージ

  • 回転する 3D アバター球体
  • 浮遊するスキルボックス
  • マウス操作でのインタラクティブな回転・ズーム
  • 美しいグラデーション背景
  • レスポンシブ対応

🛠 使用技術

  • React 18 + TypeScript
  • Three.js - 3D グラフィックスライブラリ
  • React Three Fiber - React で Three.js を使用するためのライブラリ
  • React Three Drei - 便利な 3D コンポーネント集

📋 前提条件

まず、開発環境のバージョンを確認しましょう:

# バージョン確認
node --version  # v20.17.0
npm --version   # 11.6.2
npx --version   # 11.6.2

🚀 プロジェクトセットアップ

1. React アプリの作成

# TypeScriptテンプレートでReactアプリを作成
npx create-react-app 3d-profile --template typescript

# プロジェクトディレクトリに移動
cd 3d-profile

2. 必要なライブラリのインストール

# 3D関連ライブラリをインストール
npm install three @react-three/fiber @react-three/drei @types/three

💻 実装

3D プロフィールコンポーネントの作成

src/components/Profile3D.tsxを作成します:

import React, { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls, Text, Box, Sphere, Torus } from "@react-three/drei";
import * as THREE from "three";

// 回転するアバター球体
function Avatar() {
  const meshRef = useRef<THREE.Mesh>(null!);

  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta * 0.5;
    meshRef.current.rotation.y += delta * 0.2;
  });

  return (
    <Sphere ref={meshRef} args={[1, 32, 32]} position={[0, 2, 0]}>
      <meshStandardMaterial color="#4f46e5" roughness={0.3} metalness={0.7} />
    </Sphere>
  );
}

// 浮遊するスキルボックス
function SkillBox({
  position,
  color,
  skill,
}: {
  position: [number, number, number];
  color: string;
  skill: string;
}) {
  const meshRef = useRef<THREE.Mesh>(null!);

  useFrame((state) => {
    meshRef.current.position.y =
      position[1] + Math.sin(state.clock.elapsedTime + position[0]) * 0.2;
    meshRef.current.rotation.y += 0.01;
  });

  return (
    <group>
      <Box ref={meshRef} args={[0.8, 0.8, 0.8]} position={position}>
        <meshStandardMaterial color={color} />
      </Box>
      <Text
        position={[position[0], position[1] - 1, position[2]]}
        fontSize={0.3}
        color="white"
        anchorX="center"
        anchorY="middle"
      >
        {skill}
      </Text>
    </group>
  );
}

// 装飾的なリング
function DecorativeRing() {
  const ringRef = useRef<THREE.Mesh>(null!);

  useFrame((state, delta) => {
    ringRef.current.rotation.z += delta * 0.3;
  });

  return (
    <Torus ref={ringRef} args={[3, 0.1, 16, 100]} position={[0, 0, -2]}>
      <meshStandardMaterial color="#10b981" wireframe />
    </Torus>
  );
}

// メインの3Dプロフィールコンポーネント
export default function Profile3D() {
  const skills = [
    {
      position: [-3, 1, 0] as [number, number, number],
      color: "#ef4444",
      skill: "React",
    },
    {
      position: [3, 1, 0] as [number, number, number],
      color: "#3b82f6",
      skill: "TypeScript",
    },
    {
      position: [-2, -1, 2] as [number, number, number],
      color: "#f59e0b",
      skill: "Three.js",
    },
    {
      position: [2, -1, 2] as [number, number, number],
      color: "#8b5cf6",
      skill: "Node.js",
    },
  ];

  return (
    <div
      style={{
        width: "100%",
        height: "100vh",
        background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
      }}
    >
      <Canvas camera={{ position: [0, 0, 8], fov: 75 }}>
        {/* 照明設定 */}
        <ambientLight intensity={0.5} />
        <pointLight position={[10, 10, 10]} intensity={1} />
        <spotLight
          position={[-10, -10, -10]}
          angle={0.15}
          penumbra={1}
          intensity={0.5}
        />

        {/* メインのプロフィール要素 */}
        <Avatar />

        {/* プロフィール名 */}
        <Text
          position={[0, 4, 0]}
          fontSize={1}
          color="white"
          anchorX="center"
          anchorY="middle"
        >
          あなたの名前
        </Text>

        {/* 職業/タイトル */}
        <Text
          position={[0, 3.2, 0]}
          fontSize={0.4}
          color="#e5e7eb"
          anchorX="center"
          anchorY="middle"
        >
          フロントエンド開発者
        </Text>

        {/* スキルボックス */}
        {skills.map((skill, index) => (
          <SkillBox
            key={index}
            position={skill.position}
            color={skill.color}
            skill={skill.skill}
          />
        ))}

        {/* 装飾リング */}
        <DecorativeRing />

        {/* カメラコントロール */}
        <OrbitControls
          enableZoom={true}
          enablePan={false}
          enableRotate={true}
          autoRotate={true}
          autoRotateSpeed={0.5}
        />
      </Canvas>

      {/* UI オーバーレイ */}
      <div
        style={{
          position: "absolute",
          top: "20px",
          left: "20px",
          color: "white",
          fontFamily: "Arial, sans-serif",
          zIndex: 1000,
        }}
      >
        <h2 style={{ margin: "0 0 10px 0", fontSize: "24px" }}>
          3D プロフィール
        </h2>
        <p style={{ margin: "0", fontSize: "14px", opacity: 0.8 }}>
          マウスでドラッグして回転、スクロールでズーム
        </p>
      </div>

      {/* 連絡先情報 */}
      <div
        style={{
          position: "absolute",
          bottom: "20px",
          right: "20px",
          color: "white",
          fontFamily: "Arial, sans-serif",
          textAlign: "right",
          zIndex: 1000,
        }}
      >
        <p style={{ margin: "5px 0", fontSize: "14px" }}>
          📧 your.email@example.com
        </p>
        <p style={{ margin: "5px 0", fontSize: "14px" }}>
          🐙 github.com/yourusername
        </p>
        <p style={{ margin: "5px 0", fontSize: "14px" }}>
          💼 linkedin.com/in/yourprofile
        </p>
      </div>
    </div>
  );
}

App.tsx の更新

import React from "react";
import Profile3D from "./components/Profile3D";
import "./App.css";

function App() {
  return (
    <div className="App">
      <Profile3D />
    </div>
  );
}

export default App;

スタイリングの調整

src/App.cssを更新:

.App {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

/* 3Dプロフィール用のグローバルスタイル */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
    "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
    "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* レスポンシブ対応 */
@media (max-width: 768px) {
  .App {
    height: 100vh;
  }
}

🎮 実行

# 開発サーバーを起動
npm start

ブラウザで http://localhost:3000 にアクセスすると、美しい 3D プロフィールサイトが表示されます!

🔧 主要機能の解説

1. useFrame フック

Three.js のアニメーションループを管理し、毎フレーム実行される処理を定義します。

2. useRef + THREE.Mesh

3D オブジェクトへの参照を保持し、直接操作できるようにします。

3. OrbitControls

マウス操作でのカメラ制御を簡単に実装できます。

4. 照明設定

  • ambientLight: 全体的な環境光
  • pointLight: 特定の点からの光源
  • spotLight: スポットライト効果

🎨 カスタマイズのポイント

色の変更

// スキルボックスの色を変更
{ position: [-3, 1, 0], color: "#your-color", skill: "Your Skill" }

アニメーション速度の調整

// 回転速度を変更
meshRef.current.rotation.y += delta * 0.5; // 0.5を変更

スキルの追加

const skills = [
  // 既存のスキル...
  { position: [0, -2, 1], color: "#ff6b6b", skill: "New Skill" },
];

📱 レスポンシブ対応

モバイルデバイスでも快適に動作するよう、CSS でレスポンシブ対応を実装しています。

🚀 デプロイ

Vercel でのデプロイ

# ビルド
npm run build

# Vercel CLIでデプロイ
npx vercel --prod

Netlify でのデプロイ

# ビルド
npm run build

# build フォルダをNetlifyにドラッグ&ドロップ

🎯 まとめ

React Three Fiber を使うことで、React の宣言的な書き方で 3D グラフィックスを実装できました。

メリット

  • ✅ React の知識があれば学習コストが低い
  • ✅ コンポーネント化で再利用しやすい
  • ✅ TypeScript で型安全な開発
  • ✅ 豊富な Drei コンポーネントで開発効率 UP

応用例

  • ポートフォリオサイト
  • 商品の 3D プレビュー
  • インタラクティブなデータ可視化
  • ゲームの UI

ぜひ、あなたのプロフィールサイトに 3D 要素を取り入れて、訪問者に印象的な体験を提供してみてください!

🔗 参考リンク


この記事が役に立ったら、ぜひ「いいね」をお願いします!🌟

Discussion