Circle IT Logo
Technology

Cara Membuat Komponen Band 3D di Next.js dengan React Three Fiber

Panduan lengkap membuat komponen band 3D yang interaktif menggunakan React Three Fiber di Next.js. Pelajari cara mengintegrasikan 3D graphics ke dalam aplikasi React Anda.

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:

bash
npm install three @react-three/fiber @react-three/drei
npm install -D @types/three

Basic Setup

1. Create Canvas Component

tsx
// 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

tsx
// 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

tsx
// 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

tsx
// 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

tsx
import { Environment } from '@react-three/drei';

export default function Scene() {
  return (
    <Canvas>
      <Environment preset="sunset" />
      <InteractiveBand />
      <OrbitControls />
    </Canvas>
  );
}

2. Post Processing Effects

tsx
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

tsx
// 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

tsx
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)

tsx
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

tsx
// 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

tsx
// 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

tsx
// 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

  1. Disable SSR: Always use dynamic import dengan ssr: false untuk React Three Fiber
  2. Optimize Geometry: Reduce polygon count untuk mobile devices
  3. Use Suspense: Implement loading states untuk better UX
  4. Memory Management: Dispose geometries dan materials yang tidak digunakan
  5. Performance Monitoring: Use React DevTools Profiler untuk identify bottlenecks

Troubleshooting

Common Issues

  1. "Module not found" errors:

    bash
    npm install --legacy-peer-deps
  2. Performance issues:

    • Reduce geometry complexity
    • Use instanced rendering
    • Implement LOD
  3. TypeScript errors:

    bash
    npm 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.

Resources