React Three Fiber — Creative Web

React Three Fiber React Three Fiber (R3F) adalah React renderer untuk Three.js. Daripada menulis kode Three.js imperatif, kamu mendeklarasikan scene 3D…

React Three Fiber (R3F) adalah renderer React untuk three.js: scene 3D ditulis sebagai komponen JSX, dan R3F yang membuat, mengubah dan membuang objek three.js-nya. Bayangin Raka memesan papan nama toko. Cara imperatif: menyuruh tukang langkah demi langkah ("potong kayu, cat cokelat, pasang tulisan di kiri"). Cara deklaratif: memberi gambar papan yang sudah jadi; saat harga di gambar berubah, tukang cuma mengganti angkanya. React adalah tukang itu untuk DOM, R3F tukang yang sama untuk three.js.

Di lesson ini etalase Raka dari lesson sebelumnya (mug dan kotak kaos) kita tulis ulang dengan R3F, lalu kita bandingkan dengan versi three.js biasa.

🧪 Tebakan cepat: Raka memindahkan mug dari kode three.js biasa ke R3F. Angka lampu, warna dan kameranya sama persis. Apakah hasilnya terlihat sama? Tebak dulu, jawabannya ada di bagian "Yang disiapkan Canvas".

Pasang R3F

npm install three @react-three/fiber @react-three/drei

Per Oktober 2026: @react-three/fiber 9.8.1 butuh React 19 (peer react >=19 <19.4) dan three 0.156 ke atas; @react-three/drei 10.7.9 butuh fiber 9. Proyek yang masih React 18 memakai R3F 8.

Canvas, mesh dan JSX

import { Canvas } from '@react-three/fiber';

export default function Etalase() {
  return (
    <div style={{ width: 320, height: 240 }}>
      <Canvas camera={{ position: [0, 1.2, 5.2], fov: 40 }}>
        <color attach="background" args={['#f5e6c8']} />
        <ambientLight intensity={0.8} />
        <directionalLight position={[3, 4, 5]} intensity={2.5} />
        <mesh position={[1.2, 0, 0]}>
          <boxGeometry args={[1.6, 1, 0.4]} />
          <meshStandardMaterial color="#1c1917" roughness={0.5} />
        </mesh>
      </Canvas>
    </div>
  );
}
three.js R3F
new THREE.Mesh(geo, mat) lalu scene.add <mesh> di dalam <Canvas>
new THREE.BoxGeometry(1.6, 1, 0.4) <boxGeometry args={[1.6, 1, 0.4]} /> (argumen constructor)
mesh.position.set(1.2, 0, 0) position={[1.2, 0, 0]}
mug.add(gagang) <mesh> di dalam <group>
mesh.material = [a, b, c] <meshStandardMaterial attach="material-0" /> dan seterusnya

Nama elemennya nama class three.js dengan huruf pertama kecil. Jadi <Canvas><mesh><boxGeometry /><meshStandardMaterial color="orange" /></mesh></Canvas> adalah kubus 3D berwarna oranye: mesh dengan geometry kotak dan material oranye. Supaya oranyenya terlihat, tambahkan lampu seperti di atas: tanpa lampu meshStandardMaterial hitam (lesson Geometri & Material), dan dari kamera bawaan di (0, 0, 5) kubusnya cuma tampak sebagai persegi hitam.

Yang disiapkan Canvas

<Canvas> membuat Scene, PerspectiveCamera (bawaannya fov 75 di z = 5, menghadap pusat) dan WebGLRenderer (antialias, latar transparan, dpr [1, 2]: devicePixelRatio dibatasi antara 1 dan 2), menjalankan render loop, mengikuti ukuran wadahnya (aspect + updateProjectionMatrix, lesson Three.js Setup), dan membuang objek saat komponennya hilang. Tekstur yang dipasang lewat prop map otomatis diberi SRGBColorSpace.

Satu yang sering mengejutkan: tanpa prop flat, Canvas memasang ACESFilmicToneMapping, sedangkan three.js biasa NoToneMapping. Diukur di three.js 0.186 pada etalase yang sama: sisi kaos #1e1c1b jadi #0c0a09, cangkir sablon #a76835 jadi #b76f31; latar tetap #f5e6c8. Jadi gak sama: lebih gelap dan lebih kontras. Itu jawaban tebakannya. <Canvas flat> menyamakannya.

useFrame dan useThree

import { useRef } from 'react';
import { useFrame, useThree } from '@react-three/fiber';

function Mug(props) {
  const ref = useRef();
  const invalidate = useThree((state) => state.invalidate); // ambil bagian dari state R3F
  useFrame((state, delta) => {
    ref.current.rotation.y += 0.8 * delta;  // radian per detik, sama di 60 dan 120 Hz
  });
  return <group ref={ref} {...props}>{/* badan + gagang */}</group>;
}

Event: onClick dan onPointerOver

<group
  onPointerOver={(e) => { e.stopPropagation(); setDisorot(true); }}
  onPointerOut={() => setDisorot(false)}
  onClick={(e) => { if (e.delta > 4) return; setHijau((h) => !h); }}
>

Gak ada raycaster di kodemu: R3F menghitung NDC pointer dan menjalankan Raycaster three.js ke objek yang punya handler, lalu memanggil handler-nya seperti event DOM. e.delta adalah jarak piksel sejak pointerdown, jadi klik yang sebenarnya geseran bisa disaring. e.stopPropagation() menghentikan event ke objek di belakangnya.

drei: helper siap pakai

@react-three/drei ("drei" = tiga dalam bahasa Jerman) berisi komponen siap pakai untuk R3F:

import { OrbitControls, Html, useTexture } from '@react-three/drei';

<OrbitControls enableZoom={false} />           // damping sudah nyala, update() di useFrame-nya sendiri
<Html position={[0, 1.15, 0]} center>Mug Sablon Kopi · Rp 65.000</Html> // elemen DOM yang ikut titik 3D
const peta = useTexture('/sablon-kopi.png');   // muat gambar; komponennya menunggu di <Suspense>

Lainnya: Environment (cahaya dari gambar HDRI), Text (teks 3D), useGLTF (model .glb, dibahas di lesson berikutnya).

frameloop="demand"

<Canvas frameloop="demand"> cuma menggambar saat diminta: prop berubah, invalidate() dipanggil, atau <OrbitControls> bergeser. Saat diam, nol render per detik, hemat baterai. Konsekuensinya: useFrame ikut diam, jadi mug yang berputar sendiri berhenti; dan delta frame berikutnya dihitung sejak frame terakhir yang digambar, jadi setelah 5 detik diam, mug 0,8 rad/s melompat 4 radian. Untuk animasi terus-menerus tetap pakai "always" (bawaannya).

Kapan R3F membantu, kapan gak

⚠️ Jebakan yang sering ketemu

TL;DR: R3F = three.js lewat JSX: <Canvas> menyiapkan scene, kamera (fov 75, z = 5), renderer (dpr [1, 2], latar transparan), resize, loop dan pembersihan; elemen = nama class huruf kecil, args untuk argumen constructor, props untuk properti, attach="material-0" untuk array. useFrame((state, delta) => ...) tiap frame lewat ref; useThree membaca state. Event onClick/onPointerOver di mesh: R3F yang menjalankan raycasting. drei: OrbitControls, Html, useTexture, Environment, Text. Canvas memasang ACES tone mapping kecuali flat; frameloop="demand" hemat baterai tapi menghentikan useFrame. R3F 9.8 butuh React 19; untuk halaman tanpa React, three.js biasa lebih ringan.

Yang akan kamu pelajari