Pengenalan
React Three Fiber adalah library React renderer untuk Three.js yang memungkinkan kita membuat 3D graphics dengan deklaratif menggunakan JSX. Dalam tutorial ini, kita akan membuat komponen band 3D yang interaktif untuk aplikasi Next.js.
Mengapa React Three Fiber?
- Declarative: Menggunakan JSX untuk describe 3D scenes
- Component-based: Integrate seamlessly dengan React ecosystem
- Performance: Optimized rendering dan memory management
- TypeScript Support: Fully typed untuk development experience yang lebih baik
Setup Project
Install dependencies yang diperlukan:
npm install three @react-three/fiber @react-three/drei
npm install -D @types/threeBasic Setup
1. Create Canvas Component
// app/components/Scene.tsx
'use client';
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
export default function Scene() {
return (
<div style={{ width: '100%', height: '100vh' }}>
<Canvas>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
<OrbitControls />
</Canvas>
</div>
);
}2. Create Band Component
// app/components/Band3D.tsx
'use client';
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { Mesh } from 'three';
export function Band3D() {
const meshRef = useRef<Mesh>(null);
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.x += delta * 0.5;
meshRef.current.rotation.y += delta * 0.2;
}
});
return (
<mesh ref={meshRef}>
<torusGeometry args={[2, 0.5, 16, 100]} />
<meshStandardMaterial color="#3b82f6" />
</mesh>
);
}Advanced Band Component
Textured Band
// app/components/TexturedBand.tsx
'use client';
import { useRef } from 'react';
import { useFrame, useLoader } from '@react-three/fiber';
import { Mesh, TextureLoader } from 'three';
export function TexturedBand() {
const meshRef = useRef<Mesh>(null);
// Load texture
const texture = useLoader(TextureLoader, '/textures/band-texture.jpg');
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.x += delta * 0.3;
meshRef.current.rotation.y += delta * 0.2;
}
});
return (
<mesh ref={meshRef}>
<torusGeometry args={[2, 0.5, 32, 100]} />
<meshStandardMaterial map={texture} />
</mesh>
);
}Interactive Band
// app/components/InteractiveBand.tsx
'use client';
import { useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
import { Mesh } from 'three';
export function InteractiveBand() {
const meshRef = useRef<Mesh>(null);
const [hovered, setHovered] = useState(false);
const [active, setActive] = useState(false);
useFrame((state, delta) => {
if (meshRef.current) {
meshRef.current.rotation.x += delta * (hovered ? 1 : 0.5);
meshRef.current.rotation.y += delta * 0.2;
// Smooth scale animation
const targetScale = active ? 1.5 : hovered ? 1.2 : 1;
meshRef.current.scale.x += (targetScale - meshRef.current.scale.x) * 0.1;
meshRef.current.scale.y += (targetScale - meshRef.current.scale.y) * 0.1;
meshRef.current.scale.z += (targetScale - meshRef.current.scale.z) * 0.1;
}
});
return (
<mesh
ref={meshRef}
onClick={() => setActive(!active)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<torusGeometry args={[2, 0.5, 32, 100]} />
<meshStandardMaterial
color={active ? '#ef4444' : hovered ? '#8b5cf6' : '#3b82f6'}
roughness={0.3}
metalness={0.8}
/>
</mesh>
);
}Advanced Features
1. Environment Lighting
import { Environment } from '@react-three/drei';
export default function Scene() {
return (
<Canvas>
<Environment preset="sunset" />
<InteractiveBand />
<OrbitControls />
</Canvas>
);
}2. Post Processing Effects
import { EffectComposer, Bloom } from '@react-three/postprocessing';
export default function Scene() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<InteractiveBand />
<EffectComposer>
<Bloom
intensity={1.5}
luminanceThreshold={0.9}
luminanceSmoothing={0.9}
/>
</EffectComposer>
<OrbitControls />
</Canvas>
);
}3. Multiple Bands with Animation
// app/components/BandGroup.tsx
'use client';
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { Group } from 'three';
export function BandGroup() {
const groupRef = useRef<Group>(null);
useFrame((state) => {
if (groupRef.current) {
groupRef.current.rotation.y = state.clock.elapsedTime * 0.5;
}
});
return (
<group ref={groupRef}>
<mesh position={[0, 0, 0]}>
<torusGeometry args={[2, 0.3, 32, 100]} />
<meshStandardMaterial color="#3b82f6" metalness={0.8} roughness={0.2} />
</mesh>
<mesh position={[0, 0, 0]} rotation={[Math.PI / 2, 0, 0]}>
<torusGeometry args={[2, 0.3, 32, 100]} />
<meshStandardMaterial color="#8b5cf6" metalness={0.8} roughness={0.2} />
</mesh>
<mesh position={[0, 0, 0]} rotation={[0, Math.PI / 2, 0]}>
<torusGeometry args={[2, 0.3, 32, 100]} />
<meshStandardMaterial color="#ec4899" metalness={0.8} roughness={0.2} />
</mesh>
</group>
);
}Performance Optimization
1. Use Instance Rendering
import { Instances, Instance } from '@react-three/drei';
export function OptimizedBands() {
return (
<Instances>
<torusGeometry args={[2, 0.3, 16, 50]} />
<meshStandardMaterial />
{Array.from({ length: 50 }).map((_, i) => (
<Instance
key={i}
position={[
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5,
]}
rotation={[
Math.random() * Math.PI,
Math.random() * Math.PI,
0,
]}
scale={0.5}
/>
))}
</Instances>
);
}2. Level of Detail (LOD)
import { Detailed } from '@react-three/drei';
export function LODBand() {
return (
<Detailed distances={[0, 10, 20]}>
{/* High detail */}
<mesh>
<torusGeometry args={[2, 0.5, 64, 200]} />
<meshStandardMaterial color="#3b82f6" />
</mesh>
{/* Medium detail */}
<mesh>
<torusGeometry args={[2, 0.5, 32, 100]} />
<meshStandardMaterial color="#3b82f6" />
</mesh>
{/* Low detail */}
<mesh>
<torusGeometry args={[2, 0.5, 16, 50]} />
<meshStandardMaterial color="#3b82f6" />
</mesh>
</Detailed>
);
}Responsive Design
// app/components/ResponsiveScene.tsx
'use client';
import { Canvas } from '@react-three/fiber';
import { useMediaQuery } from '@/hooks/useMediaQuery';
export default function ResponsiveScene() {
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div style={{ width: '100%', height: '100vh' }}>
<Canvas
camera={{
position: isMobile ? [0, 0, 8] : [0, 0, 5],
fov: isMobile ? 75 : 50
}}
dpr={isMobile ? [1, 1] : [1, 2]}
>
<ambientLight intensity={0.5} />
<InteractiveBand />
<OrbitControls enableZoom={!isMobile} />
</Canvas>
</div>
);
}Integration dengan Next.js
App Router Setup
// app/3d-band/page.tsx
import dynamic from 'next/dynamic';
const Scene = dynamic(() => import('@/components/Scene'), {
ssr: false,
loading: () => (
<div style={{
width: '100%',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
Loading 3D Scene...
</div>
),
});
export default function BandPage() {
return (
<main>
<h1>3D Band Component</h1>
<Scene />
</main>
);
}With Metadata
// app/3d-band/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: '3D Band Component',
description: 'Interactive 3D band created with React Three Fiber',
};Best Practices
- Disable SSR: Always use
dynamicimport denganssr: falseuntuk React Three Fiber - Optimize Geometry: Reduce polygon count untuk mobile devices
- Use Suspense: Implement loading states untuk better UX
- Memory Management: Dispose geometries dan materials yang tidak digunakan
- Performance Monitoring: Use React DevTools Profiler untuk identify bottlenecks
Troubleshooting
Common Issues
-
"Module not found" errors:
bashnpm install --legacy-peer-deps -
Performance issues:
- Reduce geometry complexity
- Use instanced rendering
- Implement LOD
-
TypeScript errors:
bashnpm install -D @types/three
Kesimpulan
Membuat 3D components dengan React Three Fiber di Next.js memberikan possibilities yang endless untuk create engaging dan interactive user experiences. Dengan mengikuti best practices dan optimization techniques, Anda dapat build performant 3D applications.