はじめに
本日はクリスマスなので、React Three Fiber (R3F)を使って、美しく輝く3Dクリスマスツリーを作ってみましょう。
どんなのを作るの?
以下のようなものを考えました。
- 星空を背景にした3Dクリスマスツリー
- きらめくオーナメントボール
- 落ちてくる雪の結晶
- マウスでの視点操作
React Three Fiber (R3F)
とは?
React Three Fiber
は、JSの3DライブラリThree.js
をReactで扱いやすくするためのライブラリです。Reactの宣言的な記法でThree.jsを扱えるため、複雑な3Dシーンも直感的に実装できます。
実装
プロジェクトのセットアップ
まず、Vite
を使って新しいReactプロジェクト
を作成します
今回は、React + JS
を使用します
npm create vite@latest christmas-app --template react
cd christmas-app
次に、必要なパッケージをインストールします
npm install react-router-dom
npm install @react-three/fiber @react-three/drei
コンポーネントの実装
1. アプリケーションのルーティング設定
App.jsx
でルーティングを設定し、ChristmasHome
コンポーネントを表示します
App.jsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ChristmasHome from './components/ChristmasHome.jsx';
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<ChristmasHome />} />
</Routes>
</Router>
);
}
export default App;
2. ChristmasHome
コンポーネントの実装
src
にcomponents/ChristmasHome.jsx
を作成します。
ChristmasHome.jsx
は以下のようなものになります。
ChristmasHome.jsx
import React, { useRef, Suspense, useMemo } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import {
Stars,
OrbitControls,
Text3D,
Sparkles,
Environment,
} from '@react-three/drei';
// 雪の結晶コンポーネント
function Snowflake() {
const meshRef = useRef(null);
const speed = Math.random() * 0.02 + 0.01;
const rotationSpeed = Math.random() * 0.02;
const startPosition = useMemo(() => ({
x: Math.random() * 20 - 10,
y: Math.random() * 10 + 5,
z: Math.random() * 20 - 10,
}), []);
useFrame(() => {
if (meshRef.current) {
meshRef.current.position.y -= speed;
meshRef.current.rotation.z += rotationSpeed;
meshRef.current.rotation.x += rotationSpeed * 0.5;
if (meshRef.current.position.y < -5) {
meshRef.current.position.set(
startPosition.x,
startPosition.y,
startPosition.z
);
}
}
});
return (
<mesh ref={meshRef} position={[startPosition.x, startPosition.y, startPosition.z]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshStandardMaterial
color="white"
emissive="white"
emissiveIntensity={0.2}
transparent
opacity={0.8}
/>
</mesh>
);
}
// 雪の降る演出
function Snowfall() {
return Array.from({ length: 200 }, (_, i) => <Snowflake key={i} />);
}
// きらめくオーナメントコンポーネント
function Ornament({ position, color }) {
return (
<mesh position={position}>
<sphereGeometry args={[0.15, 16, 16]} />
<meshStandardMaterial
color={color}
metalness={0.8}
roughness={0.2}
emissive={color}
emissiveIntensity={0.4}
/>
</mesh>
);
}
// クリスマスツリーコンポーネント
function ChristmasTree() {
const treeRef = useRef();
const colors = ['#ff0000', '#ffd700', '#00ff00', '#ff69b4', '#4169e1'];
useFrame(({ clock }) => {
if (treeRef.current) {
treeRef.current.rotation.y = clock.getElapsedTime() * 0.2;
treeRef.current.position.y = Math.sin(clock.getElapsedTime()) * 0.1 - 1.5;
}
});
return (
<group ref={treeRef} position={[0, -1.5, 0]}>
{/* ツリーの層 */}
{[2.5, 2, 1.5, 1].map((size, index) => (
<mesh key={`tree-${index}`} position={[0, index * 1.2, 0]}>
<coneGeometry args={[size, 2, 8]} />
<meshStandardMaterial
color="#006400"
roughness={0.8}
metalness={0.2}
/>
</mesh>
))}
{/* 幹 */}
<mesh position={[0, -1, 0]}>
<cylinderGeometry args={[0.3, 0.4, 1]} />
<meshStandardMaterial color="#4a3000" />
</mesh>
{/* オーナメント */}
{Array.from({ length: 30 }).map((_, i) => {
const theta = Math.random() * Math.PI * 2;
const y = Math.random() * 5;
const radius = 2.5 * (1 - y / 5);
return (
<Ornament
key={`ornament-${i}`}
position={[
radius * Math.cos(theta),
y - 1,
radius * Math.sin(theta),
]}
color={colors[i % colors.length]}
/>
);
})}
{/* トップスター */}
<mesh position={[0, 4.8, 0]}>
<sphereGeometry args={[0.3, 16, 16]} />
<meshStandardMaterial
color="#ffd700"
emissive="#ffd700"
emissiveIntensity={1}
metalness={0.9}
roughness={0.1}
/>
</mesh>
</group>
);
}
// メインコンポーネント
function ChristmasHome() {
return (
<div className="fixed inset-0" style={{ height: '100svh', width:'100vw' }}>
<Canvas camera={{ position: [0, 2, 12], fov: 60 }}>
<color attach="background" args={['#000924']} />
<ambientLight intensity={0.5} />
<Suspense fallback={null}>
{/* 星空 */}
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
{/* 環境ライト */}
<Environment preset="night" />
{/* 雪 */}
<Snowfall />
{/* クリスマスツリー */}
<ChristmasTree />
{/* Merry Christmas メッセージ */}
<Text3D font="https://threejs.org/examples/fonts/helvetiker_regular.typeface.json" size={0.8} height={0.3} position={[0, -4, 0]}>
Merry Christmas!
<meshStandardMaterial color="#FFD700" />
</Text3D>
{/* きらめきエフェクト */}
<Sparkles count={50} scale={[5, 5, 5]} size={5} color="gold" speed={1} />
</Suspense>
<OrbitControls enableZoom={false} enablePan={false} />
</Canvas>
</div>
);
}
export default ChristmasHome;
Snowflake
コンポーネント(雪の結晶)
- 雪の結晶を表現します。ランダムな位置に雪の結晶が生成され、ゆっくりと下に落ちていきます。落ちた雪の結晶は、画面の上部に戻る動きを繰り返します。
-
useFrame
を使用してアニメーションを管理。useMemo
でランダムな初期位置を設定しています。 -
sphereGeometry
で雪を表す球を作成しています。
// 雪の結晶コンポーネント
function Snowflake() {
const meshRef = useRef(null);
const speed = Math.random() * 0.02 + 0.01;
const rotationSpeed = Math.random() * 0.02;
const startPosition = useMemo(() => ({
x: Math.random() * 20 - 10,
y: Math.random() * 10 + 5,
z: Math.random() * 20 - 10,
}), []);
useFrame(() => {
if (meshRef.current) {
meshRef.current.position.y -= speed;
meshRef.current.rotation.z += rotationSpeed;
meshRef.current.rotation.x += rotationSpeed * 0.5;
if (meshRef.current.position.y < -5) {
meshRef.current.position.set(
startPosition.x,
startPosition.y,
startPosition.z
);
}
}
});
return (
<mesh ref={meshRef} position={[startPosition.x, startPosition.y, startPosition.z]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshStandardMaterial
color="white"
emissive="white"
emissiveIntensity={0.2}
transparent
opacity={0.8}
/>
</mesh>
);
}
2. Snowfall
コンポーネント(雪の降る演出)
- 200個の雪の結晶をランダムに降らせる演出を作成します。
function Snowfall() {
return Array.from({ length: 200 }, (_, i) => <Snowflake key={i} />);
}
3. Ornament
コンポーネント(きらめくオーナメント)
- 引数で与えられた場所に、引数で与えられた色の球を作成します。
function Ornament({ position, color }) {
return (
<mesh position={position}>
<sphereGeometry args={[0.15, 16, 16]} />
<meshStandardMaterial
color={color}
metalness={0.8}
roughness={0.2}
emissive={color}
emissiveIntensity={0.4}
/>
</mesh>
);
}
4. ChristmasTree
コンポーネント(クリスマスツリー)
クリスマスツリーを構築します。
- ツリーは
coneGeometry
を使用し、複数の円錐形を組み合わせで作成しました。 - ランダムな位置にオーナメントを配置し、ツリーの頭には星のように黄色く輝く球体をつけました。
-
useFrame
を使用してツリーはゆっくりと回転し、上下に揺れる動きを再現しました。
function ChristmasTree() {
const treeRef = useRef();
const colors = ['#ff0000', '#ffd700', '#00ff00', '#ff69b4', '#4169e1'];
useFrame(({ clock }) => {
if (treeRef.current) {
treeRef.current.rotation.y = clock.getElapsedTime() * 0.2;
treeRef.current.position.y = Math.sin(clock.getElapsedTime()) * 0.1 - 1.5;
}
});
return (
<group ref={treeRef} position={[0, -1.5, 0]}>
{[2.5, 2, 1.5, 1].map((size, index) => (
<mesh key={`tree-${index}`} position={[0, index * 1.2, 0]}>
<coneGeometry args={[size, 2, 8]} />
<meshStandardMaterial
color="#006400"
roughness={0.8}
metalness={0.2}
/>
</mesh>
))}
<mesh position={[0, -1, 0]}>
<cylinderGeometry args={[0.3, 0.4, 1]} />
<meshStandardMaterial color="#4a3000" />
</mesh>
{Array.from({ length: 30 }).map((_, i) => {
const theta = Math.random() * Math.PI * 2;
const y = Math.random() * 5;
const radius = 2.5 * (1 - y / 5);
return (
<Ornament
key={`ornament-${i}`}
position={[
radius * Math.cos(theta),
y - 1,
radius * Math.sin(theta),
]}
color={colors[i % colors.length]}
/>
);
})}
<mesh position={[0, 4.8, 0]}>
<sphereGeometry args={[0.3, 16, 16]} />
<meshStandardMaterial
color="#ffd700"
emissive="#ffd700"
emissiveIntensity={1}
metalness={0.9}
roughness={0.1}
/>
</mesh>
</group>
);
}
5. ChristmasHome
メインコンポーネント
3Dシーン全体を構成します。
-
Canvas
によって、シーン内のライトや背景、オブジェクト(雪、ツリー、文字、きらめきエフェクトなど)を描画します。 -
Suspense
で非同期にロードされるオブジェクトを待機します。 -
react-three/drei
の関数使用-
Stars
で簡単に星空を表現できるものです。 -
Environment
でプリセットを使用して夜風の雰囲気を簡単に表現しました。 -
Text3D
を使用して3Dテキストを表示ました。 -
Sparkles
でクリスマスツリー周りのキラキラしたエフェクトを表現しました
-
function ChristmasHome() {
return (
<div className="fixed inset-0" style={{ height: '100svh', width:'100vw' }}>
<Canvas camera={{ position: [0, 2, 12], fov: 60 }}>
<color attach="background" args={['#000924']} />
<ambientLight intensity={0.5} />
<Suspense fallback={null}>
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
<Environment preset="night" />
<Snowfall />
<ChristmasTree />
<Text3D font="https://threejs.org/examples/fonts/helvetiker_regular.typeface.json" size={0.8} height={0.3} position={[0, -4, 0]}>
Merry Christmas!
<meshStandardMaterial color="#FFD700" />
</Text3D>
<Sparkles count={50} scale={[5, 5, 5]} size={5} color="gold" speed={1} />
</Suspense>
<OrbitControls enableZoom={false} enablePan={false} />
</Canvas>
</div>
);
}