| name | react-three-fiber |
| description | React Three Fiber (R3F) + Three.js + drei 最佳實踐。當需要建立 3D 場景、處理相機/光照、互動事件、動畫、效能優化時使用。 |
React Three Fiber 最佳實踐
基於 2025/5 知識。版本基準:three ^0.170、@react-three/fiber ^9、@react-three/drei ^10。
安裝
pnpm add three @react-three/fiber @react-three/drei
pnpm add -D @types/three
基本 Canvas
import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment } from '@react-three/drei'
<Canvas camera={{ position: [5, 5, 5], fov: 50 }} dpr={[1, 2]} shadows>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 10, 5]} castShadow />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
<OrbitControls />
</Canvas>
核心 Hooks
useFrame((state, delta) => {
ref.current.rotation.y += delta
})
const { camera, gl, scene, size } = useThree()
drei 常用
OrbitControls / TrackballControls:相機控制
PerspectiveCamera makeDefault:自訂主相機
Environment preset="city":HDR 環境光
Bounds:自動 fit 物件入鏡
Html:3D 裡嵌 DOM( tooltip/label)
useGLTF('/model.glb'):載 GLTF,記得 useGLTF.preload()
Instances / Instance:大量重複物件用 instancing
互動事件
<mesh
onPointerDown={(e) => { e.stopPropagation(); }}
onPointerOver={(e) => setHover(true)}
onPointerMissed={() => setSelected(null)}
>
事件會冒泡,要 stopPropagation() 阻止穿透。onPointerMissed 在點空白處觸發(適合取消選取)。
群組旋轉(魔術方塊關鍵)
const group = useRef<THREE.Group>(null!)
const pivot = useRef<THREE.Object3D>(new THREE.Object3D())
function rotateFace(cubies: THREE.Object3D[], axis: 'x'|'y'|'z', angle: number) {
cubies.forEach(c => pivot.current.attach(c))
}
關鍵:attach()(非 add())會保留世界座標。
效能陷阱
- useFrame 裡 setState:會觸發 React render,每幀 60 次 → 災難。用 ref 直接操作。
- 每幀 new 物件:
new Vector3() 放元件頂層或 useMemo,不要 useFrame 內建。
- 同 geometry/material 重複建:extract 成常數或 useMemo。
- shadow 開太多光源:每個 castShadow 光源一次深度 pass。
- dpr 固定 2:改
dpr={[1, 2]} 讓 R3F 自適應。
- 大 scene 不用 instancing:>100 重複物件請用
<Instances>。
- HMR after geometry change:有時要 key prop 強制 remount。
動畫
- 簡單 lerp:useFrame +
THREE.MathUtils.lerp
- 複雜:
@react-spring/three(declarative)或 framer-motion-3d
- Tween:自己 state machine + useFrame
TypeScript
R3F v9 以後 primitive 元素型別在 @react-three/fiber 的 ThreeElements 已正確。若要擴充自訂元素:
import { extend } from '@react-three/fiber'
import { MyMaterial } from './myMaterial'
extend({ MyMaterial })
declare module '@react-three/fiber' {
interface ThreeElements { myMaterial: ThreeElement<typeof MyMaterial> }
}
測試策略
R3F 元件難 unit test → 把 狀態/邏輯 拆出純函式測(如魔術方塊的 rotate 邏輯),元件只負責渲染。必要時用 @react-three/test-renderer。