Open13
memo:プロジェクト制作

プロジェクト概要
プロジェクトの概要
完成イメージはThree.js 公式ドキュメントのexample ページ
- メインページ: プロジェクト一覧を表示
- 個別プロジェクトページ: Three.js シーンを表示、インタラクティブに操作可能
- レスポンシブ対応

技術スタック
使用する技術スタック
- Next.js(AppRauter): SSRとルーティングのため
- React: UIコンポーネント構築
- Three.js & React Three Fiber: 3Dコンテンツの実装
- Tailwind CSS: スタイリング
- shadcn/ui: 高品質なUIコンポーネント
- biome: リンター及びフォーマッター
- bun: パッケージマネージャー

プロジェクトの作成
Next.js でプロジェクトを作成
bunx create-next-app@latest
✔ What is your project named?
// プロジェクト名は? => three-js-showcase
✔ Would you like to use TypeScript? … No / Yes
// TypeScriptを使用しますか? => Yes
✔ Would you like to use ESLint? … No / Yes
// ESLintを使用しますか? => No (bimoe使用のため)
✔ Would you like to use Tailwind CSS? … No / Yes
// Tailwind CSSを使用しますか? => Yes
✔ Would you like to use `src/` directory? … No / Yes
// src/ ディレクトリを使用しますか? => No
✔ Would you like to use App Router? (recommended) … No / Yes
// App Routerを使用しますか? => Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
// カスタマイズされたデフォルトのインポートエイリアスを使用しますか? => No

依存関係のインストール
依存関係のインストールを行う
まずはプロジェクトのディレクトリに移動
cd three-js-showcase(ファイルのパスを指定)
Three.js 関連パッケージのインストール
bun add three @react-three/fiber @react-three/drei
- three => Three.js (3Dグラフィックスを JavaScript で書きやすくしたライブラリ)
- @react-three/fiber => React Three Fiber (Three.js を React で使えるようにしたライブラリ)
- @react-three/drei => React Three Fiber の便利ツール郡
Three.js の型定義のインストール
bun add --save @types/three
TypeScriptを使用するので型定義をインストールしておく
shadcn/ui の初期化
bunx shadcn@latest init
✔ Which style would you like to use? › New York
// どのスタイルにしますか? => New York
✔ Which color would you like to use as the base color? › Zinc
// ベースカラーはどうしますか? => Zinc
✔ Would you like to use CSS variables for theming? … no / yes
// CSS 変数は使用しますか => Yes
biome のインストールと初期化
bun add --save --exact @biomejs/biome
nunx biome init

プロジェクト構造の設定
プロジェクト構造の設定を行う
ディレクトリ構造の作成
/three-js-showcase
/app
/projects
/[id]
layout.tsx
page.tsx
/components
/lib
/public
/models
/textures
/types

app/layout.tsx の更新
メタデータの更新を行う
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Thee.js Project Showcase',
description: 'Explore amazing Three.js projects',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="ja">
<body>{children}</body>
</html>
)
}

app/page.tsx の更新
page.tsx にスタイルを当てる
import Navigation from '@/components/Navigation'
export default function Home() {
return (
<div className=" flex h-screen bg-gray-100 text-gray-800">
<nav className="w-64 bg-white shadow-md">
<Navigation />
</nav>
<main className="flex-1 flex flex-col">
<div>
<div>{/* <Canvas/> */}</div>
</div>
</main>
</div>
)
}
Navigation コンポーネントと Canvas コンポーネントは未実装なのでコメントアウト
- Navigation コンポーネント: サイドバーナビゲーション サンプルイメージを表示する
- Canvas コンポーネント: 3Dシーンを描画する
完成イメージ

Navigation.tsx の作成
Navigation コンポーネントの作成
export default function Navigation() {
return (
<nav className="container mx-auto px-4">
<h1 className="text-xl font-bold border-b mt-4 mb-8">
Three.js Project Showcase
</h1>
<ul className="space-y-2">
<li>
<div className="flex flex-col border rounded-lg mb-4 bg-gray-200">
<img
className="aspect-video object-cover object-center "
src=""
alt=""
/>
<p className="p-2 bg-white">title 1</p>
</div>
</li>
<li>
<div className="flex flex-col border rounded-lg mb-4 bg-gray-200">
<img
className="aspect-video object-cover object-center"
src=""
alt=""
/>
<p className="p-2 bg-white">title 1</p>
</div>
</li>
<li>
<div className="flex flex-col border rounded-lg mb-4 bg-gray-200">
<img
className="aspect-video object-cover object-center "
src=""
alt=""
/>
<p className="p-2 bg-white">title 1</p>
</div>
</li>
</ul>
</nav>
)
}
完成イメージ

Canvas3D.tsx の作成
Canvas コンポーネントにするつもりだったが
React Three Fiber のCanvas と名前衝突するので Canvas3Dコンポーネントに変更
'use client'
import { OrbitControls } from '@react-three/drei'
import { Canvas } from '@react-three/fiber'
import { Suspense } from 'react'
import { CineonToneMapping, SRGBColorSpace } from 'three/webgpu'
function Box() {
return (
<mesh>
<boxGeometry args={[2, 2, 2]} /> // ジオメトリの設定 配列内は[width(横幅), height(高さ), depth(奥行き)]
<meshBasicMaterial color={'red'} /> // マテリアルの設定
</mesh>
)
}
export default function Canvas3D() {
return (
<div className="w-full h-screen">
<Canvas
gl={{
antialias: true, // アンチエイリアスの有効化
toneMapping: CineonToneMapping, // トーンマッピングの設定
outputColorSpace: SRGBColorSpace, // カラースペースの設定
}}
>
<Suspense fallback={null}>
<Box />
<OrbitControls />
</Suspense>
</Canvas>
</div>
)
}
React Three Fiber は未学習なので生成AIにお願いした
Three.js は学習したのである程度の意味はわかる

app/layout.tsx の更新
常にサイドバーが表示されるように変更
import Navigation from '@/components/Navigation'
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Thee.js Project Showcase',
description: 'Explore amazing Three.js projects',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="ja">
<body>
<div className="flex h-screen bg-gray-100 text-gray-800">
<div className="w-64 bg-white shadow-md">
<Navigation />
</div>
<main className="flex-1 overflow-y-auto">
<div>
<div>{children}</div>
</div>
</main>
</div>
</body>
</html>
)
}

Nacigation.tsx の更新
デザインの追加と簡単なリファクタリング
画像を追加したりmap メソッドで配列を回して表示したり
import Image from 'next/image'
import Link from 'next/link'
export type Project = {
id: string
title: string
imageSrc: string
}
const projects: Project[] = [
{ id: 'cube', title: 'cube', imageSrc: '/thumbnails/box.png' },
{ id: 'sphere', title: 'Sphere', imageSrc: '/thumbnails/sphere.png' },
{ id: 'torus', title: 'Torus', imageSrc: '/thumbnails/torus.png' },
]
interface NavigationProps {
onProjectSlect: (project: Project) => void
}
export default function Navigation() {
return (
<nav className="container mx-auto px-4">
<h1 className="text-xl font-bold border-b mt-4 mb-8">
Three.js Project Showcase
</h1>
<ul className="space-y-2">
{projects.map((project) => (
<li key={project.id}>
<Link href={`/projects/${project.id}`}>
<div className="flex flex-col border rounded-lg relative mb-4 bg-gray-200 hover:bg-gray-300 transition-colors">
<Image
className="aspect-video object-cover object-center"
src={project.imageSrc}
alt={`${project.title} thumbnail`}
width={300}
height={169}
/>
<p className="p-2 bg-white">{project.title}</p>
</div>
</Link>
</li>
))}
</ul>
</nav>
)
}

3Dオブジェクトの作成
テストオブジェクトの作成しコンポーネントとして保存
componets/3d-object/Cube.tsx
export default function Cube() {
return (
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={'blue'} />
</mesh>
)
}
componets/3d-object/Spherer.tsx
export default function Sphere() {
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshStandardMaterial color={'blue'} />
</mesh>
)
}
componets/3d-object/Torus.tsx
export default function Torus() {
return (
<mesh>
<torusGeometry args={[1, 0.5, 64, 64]} />
<meshStandardMaterial color={'blue'} />
</mesh>
)
}

動的ルーティングで表示するオブジェクトの切り替え
/[id] を使用した動的なルーティング
[id]がproject/id
として適用される
project/[id]/page.tsx
'use client'
import Canvas3D from '@/components/Canvas3D'
export default function ProjectPage({ params }: { params: { id: string } }) {
return (
<div>
<Canvas3D projectId={params.id} />
</div>
)
}