react-three-fiber
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Three Fiber
React Three Fiber
Overview
概述
React Three Fiber (R3F) is a React renderer for Three.js that brings declarative, component-based 3D development to React applications. Instead of imperatively creating and managing Three.js objects, you build 3D scenes using JSX components that map directly to Three.js objects.
When to Use This Skill:
- Building 3D experiences within React applications
- Creating interactive product configurators or showcases
- Developing 3D portfolios, galleries, or storytelling experiences
- Building games or simulations in React
- Adding 3D elements to existing React projects
- When you need state management and React hooks with 3D graphics
- When working with React frameworks (Next.js, Gatsby, Remix)
Key Benefits:
- Declarative: Write 3D scenes like React components
- React Integration: Full access to hooks, context, state management
- Reusability: Create and share 3D component libraries
- Performance: Automatic render optimization and reconciliation
- Ecosystem: Works with Drei helpers, Zustand, Framer Motion, etc.
- TypeScript Support: Full type safety for Three.js objects
React Three Fiber(R3F)是Three.js的一款React渲染器,它将声明式、组件化的3D开发带入React应用中。无需以命令式方式创建和管理Three.js对象,你可以使用JSX组件构建3D场景,这些组件直接映射到Three.js对象。
何时使用该技能:
- 在React应用内构建3D体验
- 创建交互式产品配置器或展示页
- 开发3D作品集、画廊或叙事体验
- 在React中构建游戏或模拟场景
- 为现有React项目添加3D元素
- 当你需要将状态管理和React钩子与3D图形结合时
- 配合React框架(Next.js、Gatsby、Remix)使用时
核心优势:
- 声明式: 像编写React组件一样编写3D场景
- React集成: 全面访问钩子、上下文、状态管理功能
- 可复用性: 创建并共享3D组件库
- 性能: 自动渲染优化与协调
- 生态系统: 兼容Drei工具库、Zustand、Framer Motion等
- TypeScript支持: 为Three.js对象提供完整的类型安全
Core Concepts
核心概念
1. Canvas Component
1. Canvas组件
The component sets up a Three.js scene, camera, renderer, and render loop.
<Canvas>jsx
import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
camera={{ position: [0, 0, 5], fov: 75 }}
gl={{ antialias: true }}
dpr={[1, 2]}
>
{/* 3D content goes here */}
</Canvas>
)
}Canvas Props:
- - Camera configuration (position, fov, near, far)
camera - - WebGL renderer settings
gl - - Device pixel ratio (default: [1, 2])
dpr - - Enable shadow mapping (default: false)
shadows - - "always" (default), "demand", or "never"
frameloop - - Disable color management for simpler colors
flat - - Use linear color space instead of sRGB
linear
<Canvas>jsx
import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
camera={{ position: [0, 0, 5], fov: 75 }}
gl={{ antialias: true }}
dpr={[1, 2]}
>
{/* 3D内容写在这里 */}
</Canvas>
)
}Canvas属性:
- - 相机配置(位置、视场角、近裁面、远裁面)
camera - - WebGL渲染器设置
gl - - 设备像素比(默认值:[1, 2])
dpr - - 启用阴影映射(默认值:false)
shadows - - 取值为"always"(默认)、"demand"或"never"
frameloop - - 禁用色彩管理以简化颜色显示
flat - - 使用线性色彩空间而非sRGB
linear
2. Declarative 3D Objects
2. 声明式3D对象
Three.js objects are created using JSX with kebab-case props:
jsx
// THREE.Mesh + THREE.BoxGeometry + THREE.MeshStandardMaterial
<mesh position={[0, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>Prop Mapping:
- →
positionobject.position.set(x, y, z) - →
rotationobject.rotation.set(x, y, z) - →
scaleobject.scale.set(x, y, z) - → Constructor arguments for geometry/material
args - → Attach to parent property (e.g.,
attach)attach="material"
Shorthand Notation:
jsx
// Full notation
<mesh position={[1, 2, 3]} />
// Axis-specific (dash notation)
<mesh position-x={1} position-y={2} position-z={3} />使用JSX创建Three.js对象,属性采用短横线命名法:
jsx
// THREE.Mesh + THREE.BoxGeometry + THREE.MeshStandardMaterial
<mesh position={[0, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>属性映射:
- →
positionobject.position.set(x, y, z) - →
rotationobject.rotation.set(x, y, z) - →
scaleobject.scale.set(x, y, z) - → 几何体/材质的构造函数参数
args - → 附加到父对象的属性(例如:
attach)attach="material"
简写语法:
jsx
// 完整写法
<mesh position={[1, 2, 3]} />
// 按轴指定(短横线写法)
<mesh position-x={1} position-y={2} position-z={3} />3. useFrame Hook
3. useFrame钩子
Execute code on every frame (animation loop):
jsx
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
// Rotate mesh on every frame
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
// Access scene state
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}useFrame Parameters:
- - Scene state (camera, scene, gl, clock, etc.)
state - - Time since last frame (for frame-rate independence)
delta - - XR frame data (for VR/AR)
xrFrame
Important: Never use inside - it causes unnecessary re-renders!
setStateuseFrame在每一帧执行代码(动画循环):
jsx
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
// 每帧旋转网格
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
// 访问场景状态
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}useFrame参数:
- - 场景状态(相机、场景、gl、时钟等)
state - - 自上一帧以来的时间(确保帧率无关性)
delta - - XR帧数据(用于VR/AR)
xrFrame
重要提示: 绝不要在内使用——这会导致不必要的重渲染!
useFramesetState4. useThree Hook
4. useThree钩子
Access scene state and methods:
jsx
import { useThree } from '@react-three/fiber'
function CameraInfo() {
const { camera, gl, scene, size, viewport } = useThree()
// Selective subscription (only re-render on size change)
const size = useThree((state) => state.size)
// Get state non-reactively
const get = useThree((state) => state.get)
const freshState = get() // Latest state without triggering re-render
return null
}Available State:
- - Default camera
camera - - Three.js scene
scene - - WebGL renderer
gl - - Canvas dimensions
size - - Viewport dimensions in 3D units
viewport - - Three.js clock
clock - - Normalized mouse coordinates
pointer - - Manually trigger render
invalidate() - - Manually resize canvas
setSize()
访问场景状态和方法:
jsx
import { useThree } from '@react-three/fiber'
function CameraInfo() {
const { camera, gl, scene, size, viewport } = useThree()
// 选择性订阅(仅在尺寸变化时重渲染)
const size = useThree((state) => state.size)
// 非响应式获取状态
const get = useThree((state) => state.get)
const freshState = get() // 获取最新状态,不会触发重渲染
return null
}可用状态:
- - 默认相机
camera - - Three.js场景
scene - - WebGL渲染器
gl - - Canvas尺寸
size - - 3D单位下的视口尺寸
viewport - - Three.js时钟
clock - - 归一化鼠标坐标
pointer - - 手动触发渲染
invalidate() - - 手动调整Canvas尺寸
setSize()
5. useLoader Hook
5. useLoader钩子
Load assets with automatic caching and Suspense integration:
jsx
import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { TextureLoader } from 'three'
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
return <primitive object={gltf.scene} />
}
function TexturedMesh() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingIndicator />}>
<Model />
<TexturedMesh />
</Suspense>
</Canvas>
)
}Loading Multiple Assets:
jsx
const [texture1, texture2, texture3] = useLoader(TextureLoader, [
'/tex1.jpg',
'/tex2.jpg',
'/tex3.jpg'
])Loader Extensions:
jsx
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
useLoader(GLTFLoader, '/model.glb', (loader) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
})Pre-loading:
jsx
// Pre-load assets before component mounts
useLoader.preload(GLTFLoader, '/model.glb')加载资源并自动缓存,同时集成Suspense:
jsx
import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { TextureLoader } from 'three'
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
return <primitive object={gltf.scene} />
}
function TexturedMesh() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return (
<mesh>
<boxGeometry />
<meshStandardMaterial map={texture} />
</mesh>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingIndicator />}>
<Model />
<TexturedMesh />
</Suspense>
</Canvas>
)
}加载多个资源:
jsx
const [texture1, texture2, texture3] = useLoader(TextureLoader, [
'/tex1.jpg',
'/tex2.jpg',
'/tex3.jpg'
])加载器扩展:
jsx
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
useLoader(GLTFLoader, '/model.glb', (loader) => {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
loader.setDRACOLoader(dracoLoader)
})预加载:
jsx
// 在组件挂载前预加载资源
useLoader.preload(GLTFLoader, '/model.glb')Common Patterns
常见模式
Pattern 1: Basic Scene Setup
模式1: 基础场景设置
jsx
import { Canvas } from '@react-three/fiber'
function Scene() {
return (
<>
{/* Lights */}
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
{/* Objects */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</>
)
}
function App() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
<Scene />
</Canvas>
)
}jsx
import { Canvas } from '@react-three/fiber'
function Scene() {
return (
<>
{/* 灯光 */}
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
{/* 对象 */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</>
)
}
function App() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
<Scene />
</Canvas>
)
}Pattern 2: Interactive Objects (Click, Hover)
模式2: 交互式对象(点击、悬停)
jsx
import { useState } from 'react'
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
scale={active ? 1.5 : 1}
onClick={() => setActive(!active)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}jsx
import { useState } from 'react'
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
scale={active ? 1.5 : 1}
onClick={() => setActive(!active)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}Pattern 3: Animated Component with useFrame
模式3: 结合useFrame的动画组件
jsx
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
function AnimatedSphere() {
const meshRef = useRef()
useFrame((state, delta) => {
// Rotate
meshRef.current.rotation.y += delta
// Oscillate position
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="cyan" />
</mesh>
)
}jsx
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
function AnimatedSphere() {
const meshRef = useRef()
useFrame((state, delta) => {
// 旋转
meshRef.current.rotation.y += delta
// 位置振荡
const time = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(time) * 2
})
return (
<mesh ref={meshRef}>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color="cyan" />
</mesh>
)
}Pattern 4: Loading GLTF Models
模式4: 加载GLTF模型
jsx
import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url)
return (
<primitive
object={gltf.scene}
scale={0.5}
position={[0, 0, 0]}
/>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingPlaceholder />}>
<Model url="/model.glb" />
</Suspense>
</Canvas>
)
}
function LoadingPlaceholder() {
return (
<mesh>
<boxGeometry />
<meshBasicMaterial wireframe />
</mesh>
)
}jsx
import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url)
return (
<primitive
object={gltf.scene}
scale={0.5}
position={[0, 0, 0]}
/>
)
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingPlaceholder />}>
<Model url="/model.glb" />
</Suspense>
</Canvas>
)
}
function LoadingPlaceholder() {
return (
<mesh>
<boxGeometry />
<meshBasicMaterial wireframe />
</mesh>
)
}Pattern 5: Multiple Lights
模式5: 多灯光设置
jsx
function Lighting() {
return (
<>
{/* Ambient light for base illumination */}
<ambientLight intensity={0.3} />
{/* Directional light with shadows */}
<directionalLight
position={[5, 5, 5]}
intensity={1}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
{/* Point light for accent */}
<pointLight position={[-5, 5, -5]} intensity={0.5} color="blue" />
{/* Spot light for focused illumination */}
<spotLight
position={[10, 10, 10]}
angle={0.3}
penumbra={1}
intensity={1}
/>
</>
)
}jsx
function Lighting() {
return (
<>
{/* 环境光:提供基础照明 */}
<ambientLight intensity={0.3} />
{/* 平行光:带阴影 */}
<directionalLight
position={[5, 5, 5]}
intensity={1}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
{/* 点光源:用于强调 */}
<pointLight position={[-5, 5, -5]} intensity={0.5} color="blue" />
{/* 聚光灯:用于聚焦照明 */}
<spotLight
position={[10, 10, 10]}
angle={0.3}
penumbra={1}
intensity={1}
/>
</>
)
}Pattern 6: Instancing (Many Objects)
模式6: 实例化(大量对象)
jsx
import { useMemo, useRef } from 'react'
import * as THREE from 'three'
import { useFrame } from '@react-three/fiber'
function Particles({ count = 1000 }) {
const meshRef = useRef()
// Generate random positions
const particles = useMemo(() => {
const temp = []
for (let i = 0; i < count; i++) {
const t = Math.random() * 100
const factor = 20 + Math.random() * 100
const speed = 0.01 + Math.random() / 200
const x = Math.random() * 2 - 1
const y = Math.random() * 2 - 1
const z = Math.random() * 2 - 1
temp.push({ t, factor, speed, x, y, z, mx: 0, my: 0 })
}
return temp
}, [count])
const dummy = useMemo(() => new THREE.Object3D(), [])
useFrame(() => {
particles.forEach((particle, i) => {
let { t, factor, speed, x, y, z } = particle
t = particle.t += speed / 2
const a = Math.cos(t) + Math.sin(t * 1) / 10
const b = Math.sin(t) + Math.cos(t * 2) / 10
const s = Math.cos(t)
dummy.position.set(
x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10,
y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10,
z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10
)
dummy.scale.set(s, s, s)
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
})
meshRef.current.instanceMatrix.needsUpdate = true
})
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}jsx
import { useMemo, useRef } from 'react'
import * as THREE from 'three'
import { useFrame } from '@react-three/fiber'
function Particles({ count = 1000 }) {
const meshRef = useRef()
// 生成随机位置
const particles = useMemo(() => {
const temp = []
for (let i = 0; i < count; i++) {
const t = Math.random() * 100
const factor = 20 + Math.random() * 100
const speed = 0.01 + Math.random() / 200
const x = Math.random() * 2 - 1
const y = Math.random() * 2 - 1
const z = Math.random() * 2 - 1
temp.push({ t, factor, speed, x, y, z, mx: 0, my: 0 })
}
return temp
}, [count])
const dummy = useMemo(() => new THREE.Object3D(), [])
useFrame(() => {
particles.forEach((particle, i) => {
let { t, factor, speed, x, y, z } = particle
t = particle.t += speed / 2
const a = Math.cos(t) + Math.sin(t * 1) / 10
const b = Math.sin(t) + Math.cos(t * 2) / 10
const s = Math.cos(t)
dummy.position.set(
x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10,
y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10,
z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10
)
dummy.scale.set(s, s, s)
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
})
meshRef.current.instanceMatrix.needsUpdate = true
})
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.05, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}Pattern 7: Groups and Nesting
模式7: 组与嵌套
jsx
function Robot() {
return (
<group position={[0, 0, 0]}>
{/* Body */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 2, 1]} />
<meshStandardMaterial color="gray" />
</mesh>
{/* Head */}
<mesh position={[0, 1.5, 0]}>
<sphereGeometry args={[0.5, 32, 32]} />
<meshStandardMaterial color="silver" />
</mesh>
{/* Arms */}
<group position={[-0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
<group position={[0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
</group>
)
}jsx
function Robot() {
return (
<group position={[0, 0, 0]}>
{/* 身体 */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[1, 2, 1]} />
<meshStandardMaterial color="gray" />
</mesh>
{/* 头部 */}
<mesh position={[0, 1.5, 0]}>
<sphereGeometry args={[0.5, 32, 32]} />
<meshStandardMaterial color="silver" />
</mesh>
{/* 手臂 */}
<group position={[-0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
<group position={[0.75, 0.5, 0]}>
<mesh>
<cylinderGeometry args={[0.1, 0.1, 1.5]} />
<meshStandardMaterial color="darkgray" />
</mesh>
</group>
</group>
)
}Integration with Drei Helpers
与Drei工具库集成
Drei is the essential helper library for R3F, providing ready-to-use components:
Drei是R3F的必备工具库,提供了即用型组件:
OrbitControls
OrbitControls
jsx
import { OrbitControls } from '@react-three/drei'
<Canvas>
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.05}
minDistance={3}
maxDistance={20}
/>
<Box />
</Canvas>jsx
import { OrbitControls } from '@react-three/drei'
<Canvas>
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.05}
minDistance={3}
maxDistance={20}
/>
<Box />
</Canvas>Environment & Lighting
环境与照明
jsx
import { Environment, ContactShadows } from '@react-three/drei'
<Canvas>
{/* HDRI environment map */}
<Environment preset="sunset" />
{/* Or custom */}
<Environment files="/hdri.hdr" />
{/* Soft contact shadows */}
<ContactShadows
opacity={0.5}
scale={10}
blur={1}
far={10}
resolution={256}
/>
<Model />
</Canvas>jsx
import { Environment, ContactShadows } from '@react-three/drei'
<Canvas>
{/* HDRI环境贴图 */}
<Environment preset="sunset" />
{/* 或自定义环境 */}
<Environment files="/hdri.hdr" />
{/* 柔和接触阴影 */}
<ContactShadows
opacity={0.5}
scale={10}
blur={1}
far={10}
resolution={256}
/>
<Model />
</Canvas>Text
文本
jsx
import { Text, Text3D } from '@react-three/drei'
// 2D Billboard text
<Text
position={[0, 2, 0]}
fontSize={1}
color="white"
anchorX="center"
anchorY="middle"
>
Hello World
</Text>
// 3D extruded text
<Text3D
font="/fonts/helvetiker_regular.typeface.json"
size={1}
height={0.2}
>
3D Text
<meshNormalMaterial />
</Text3D>jsx
import { Text, Text3D } from '@react-three/drei'
// 2D公告板文本
<Text
position={[0, 2, 0]}
fontSize={1}
color="white"
anchorX="center"
anchorY="middle"
>
Hello World
</Text>
// 3D挤出文本
<Text3D
font="/fonts/helvetiker_regular.typeface.json"
size={1}
height={0.2}
>
3D Text
<meshNormalMaterial />
</Text3D>useGLTF Hook (Drei)
useGLTF钩子(Drei)
jsx
import { useGLTF } from '@react-three/drei'
function Model() {
const { scene, materials, nodes } = useGLTF('/model.glb')
return <primitive object={scene} />
}
// Pre-load
useGLTF.preload('/model.glb')jsx
import { useGLTF } from '@react-three/drei'
function Model() {
const { scene, materials, nodes } = useGLTF('/model.glb')
return <primitive object={scene} />
}
// 预加载
useGLTF.preload('/model.glb')Center & Bounds
居中与边界
jsx
import { Center, Bounds, useBounds } from '@react-three/drei'
// Auto-center objects
<Center>
<Model />
</Center>
// Auto-fit camera to bounds
<Bounds fit clip observe margin={1.2}>
<Model />
</Bounds>jsx
import { Center, Bounds, useBounds } from '@react-three/drei'
// 自动居中对象
<Center>
<Model />
</Center>
// 自动适配相机到边界
<Bounds fit clip observe margin={1.2}>
<Model />
</Bounds>HTML Overlay
HTML叠加层
jsx
import { Html } from '@react-three/drei'
<mesh>
<boxGeometry />
<meshStandardMaterial />
<Html
position={[0, 1, 0]}
center
distanceFactor={10}
>
<div className="annotation">
This is a box
</div>
</Html>
</mesh>jsx
import { Html } from '@react-three/drei'
<mesh>
<boxGeometry />
<meshStandardMaterial />
<Html
position={[0, 1, 0]}
center
distanceFactor={10}
>
<div className="annotation">
这是一个立方体
</div>
</Html>
</mesh>Scroll Controls
滚动控制
jsx
import { ScrollControls, Scroll, useScroll } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
function AnimatedScene() {
const scroll = useScroll()
const meshRef = useRef()
useFrame(() => {
const offset = scroll.offset // 0-1 normalized scroll position
meshRef.current.position.y = offset * 10
})
return <mesh ref={meshRef}>...</mesh>
}
<Canvas>
<ScrollControls pages={3} damping={0.5}>
<Scroll>
<AnimatedScene />
</Scroll>
{/* HTML overlay */}
<Scroll html>
<div style={{ height: '100vh' }}>
<h1>Scrollable content</h1>
</div>
</Scroll>
</ScrollControls>
</Canvas>jsx
import { ScrollControls, Scroll, useScroll } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
function AnimatedScene() {
const scroll = useScroll()
const meshRef = useRef()
useFrame(() => {
const offset = scroll.offset // 0-1的归一化滚动位置
meshRef.current.position.y = offset * 10
})
return <mesh ref={meshRef}>...</mesh>
}
<Canvas>
<ScrollControls pages={3} damping={0.5}>
<Scroll>
<AnimatedScene />
</Scroll>
{/* HTML叠加内容 */}
<Scroll html>
<div style={{ height: '100vh' }}>
<h1>可滚动内容</h1>
</div>
</Scroll>
</ScrollControls>
</Canvas>Integration with Other Libraries
与其他库集成
With GSAP
与GSAP集成
jsx
import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import gsap from 'gsap'
function AnimatedBox() {
const meshRef = useRef()
useEffect(() => {
// GSAP timeline animation
const tl = gsap.timeline({ repeat: -1, yoyo: true })
tl.to(meshRef.current.position, {
y: 2,
duration: 1,
ease: 'power2.inOut'
})
.to(meshRef.current.rotation, {
y: Math.PI * 2,
duration: 2,
ease: 'none'
}, 0)
return () => tl.kill()
}, [])
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}jsx
import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import gsap from 'gsap'
function AnimatedBox() {
const meshRef = useRef()
useEffect(() => {
// GSAP时间轴动画
const tl = gsap.timeline({ repeat: -1, yoyo: true })
tl.to(meshRef.current.position, {
y: 2,
duration: 1,
ease: 'power2.inOut'
})
.to(meshRef.current.rotation, {
y: Math.PI * 2,
duration: 2,
ease: 'none'
}, 0)
return () => tl.kill()
}, [])
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}With Framer Motion
与Framer Motion集成
jsx
import { motion } from 'framer-motion-3d'
function AnimatedSphere() {
return (
<motion.mesh
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 1 }}
>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</motion.mesh>
)
}jsx
import { motion } from 'framer-motion-3d'
function AnimatedSphere() {
return (
<motion.mesh
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 1 }}
>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</motion.mesh>
)
}With Zustand (State Management)
与Zustand集成(状态管理)
jsx
import create from 'zustand'
const useStore = create((set) => ({
color: 'orange',
setColor: (color) => set({ color })
}))
function Box() {
const color = useStore((state) => state.color)
const setColor = useStore((state) => state.setColor)
return (
<mesh onClick={() => setColor('hotpink')}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}jsx
import create from 'zustand'
const useStore = create((set) => ({
color: 'orange',
setColor: (color) => set({ color })
}))
function Box() {
const color = useStore((state) => state.color)
const setColor = useStore((state) => state.setColor)
return (
<mesh onClick={() => setColor('hotpink')}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}Performance Optimization
性能优化
1. On-Demand Rendering
1. 按需渲染
jsx
<Canvas frameloop="demand">
{/* Only renders when needed */}
</Canvas>
// Manually trigger render
function MyComponent() {
const invalidate = useThree((state) => state.invalidate)
return (
<mesh onClick={() => invalidate()}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}jsx
<Canvas frameloop="demand">
{/* 仅在需要时渲染 */}
</Canvas>
// 手动触发渲染
function MyComponent() {
const invalidate = useThree((state) => state.invalidate)
return (
<mesh onClick={() => invalidate()}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}2. Instancing
2. 实例化
Use for rendering many identical objects:
<instancedMesh>jsx
function Particles({ count = 10000 }) {
const meshRef = useRef()
useEffect(() => {
const temp = new THREE.Object3D()
for (let i = 0; i < count; i++) {
temp.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
)
temp.updateMatrix()
meshRef.current.setMatrixAt(i, temp.matrix)
}
meshRef.current.instanceMatrix.needsUpdate = true
}, [count])
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.1, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}使用渲染大量相同对象:
<instancedMesh>jsx
function Particles({ count = 10000 }) {
const meshRef = useRef()
useEffect(() => {
const temp = new THREE.Object3D()
for (let i = 0; i < count; i++) {
temp.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
)
temp.updateMatrix()
meshRef.current.setMatrixAt(i, temp.matrix)
}
meshRef.current.instanceMatrix.needsUpdate = true
}, [count])
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<sphereGeometry args={[0.1, 8, 8]} />
<meshBasicMaterial color="white" />
</instancedMesh>
)
}3. Frustum Culling
3. 视锥体剔除
Objects outside the camera view are automatically culled.
jsx
// Disable for always-visible objects
<mesh frustumCulled={false}>
<boxGeometry />
<meshStandardMaterial />
</mesh>相机视野外的对象会被自动剔除。
jsx
// 对始终可见的对象禁用该功能
<mesh frustumCulled={false}>
<boxGeometry />
<meshStandardMaterial />
</mesh>4. LOD (Level of Detail)
4. LOD(细节层次)
jsx
import { Detailed } from '@react-three/drei'
<Detailed distances={[0, 10, 20]}>
{/* High detail - close to camera */}
<mesh geometry={highPolyGeometry} />
{/* Medium detail */}
<mesh geometry={mediumPolyGeometry} />
{/* Low detail - far from camera */}
<mesh geometry={lowPolyGeometry} />
</Detailed>jsx
import { Detailed } from '@react-three/drei'
<Detailed distances={[0, 10, 20]}>
{/* 高细节:靠近相机时 */}
<mesh geometry={highPolyGeometry} />
{/* 中细节 */}
<mesh geometry={mediumPolyGeometry} />
{/* 低细节:远离相机时 */}
<mesh geometry={lowPolyGeometry} />
</Detailed>5. Adaptive Performance
5. 自适应性能
jsx
import { AdaptiveDpr, AdaptiveEvents, PerformanceMonitor } from '@react-three/drei'
<Canvas>
{/* Reduce DPR when performance drops */}
<AdaptiveDpr pixelated />
{/* Reduce raycast frequency */}
<AdaptiveEvents />
{/* Monitor and respond to performance */}
<PerformanceMonitor
onIncline={() => console.log('Performance improved')}
onDecline={() => console.log('Performance degraded')}
>
<Scene />
</PerformanceMonitor>
</Canvas>jsx
import { AdaptiveDpr, AdaptiveEvents, PerformanceMonitor } from '@react-three/drei'
<Canvas>
{/* 性能下降时降低DPR */}
<AdaptiveDpr pixelated />
{/* 降低射线检测频率 */}
<AdaptiveEvents />
{/* 监控并响应性能变化 */}
<PerformanceMonitor
onIncline={() => console.log('性能提升')}
onDecline={() => console.log('性能下降')}
>
<Scene />
</PerformanceMonitor>
</Canvas>6. Selective Re-renders
6. 选择性重渲染
Use selectors to avoid unnecessary re-renders:
useThreejsx
// ❌ Re-renders on any state change
const state = useThree()
// ✅ Only re-renders when size changes
const size = useThree((state) => state.size)
// ✅ Only re-renders when camera changes
const camera = useThree((state) => state.camera)使用选择器避免不必要的重渲染:
useThreejsx
// ❌ 任何状态变化都会触发重渲染
const state = useThree()
// ✅ 仅在尺寸变化时重渲染
const size = useThree((state) => state.size)
// ✅ 仅在相机变化时重渲染
const camera = useThree((state) => state.camera)Common Pitfalls & Solutions
常见陷阱与解决方案
❌ Pitfall 1: setState in useFrame
❌ 陷阱1: 在useFrame中使用setState
jsx
// ❌ BAD: Triggers React re-renders every frame
const [x, setX] = useState(0)
useFrame(() => setX((x) => x + 0.1))
return <mesh position-x={x} />✅ Solution: Mutate refs directly
jsx
// ✅ GOOD: Direct mutation, no re-renders
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.position.x += delta
})
return <mesh ref={meshRef} />jsx
// ❌ 错误:每帧触发React重渲染
const [x, setX] = useState(0)
useFrame(() => setX((x) => x + 0.1))
return <mesh position-x={x} />✅ 解决方案: 直接修改ref
jsx
// ✅ 正确:直接修改,无重渲染
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.position.x += delta
})
return <mesh ref={meshRef} />❌ Pitfall 2: Creating Objects in Render
❌ 陷阱2: 在渲染函数中创建对象
jsx
// ❌ BAD: Creates new Vector3 every render
<mesh position={new THREE.Vector3(1, 2, 3)} />✅ Solution: Use arrays or useMemo
jsx
// ✅ GOOD: Use array notation
<mesh position={[1, 2, 3]} />
// Or useMemo for complex objects
const position = useMemo(() => new THREE.Vector3(1, 2, 3), [])
<mesh position={position} />jsx
// ❌ 错误:每次渲染都创建新的Vector3
<mesh position={new THREE.Vector3(1, 2, 3)} />✅ 解决方案: 使用数组或useMemo
jsx
// ✅ 正确:使用数组写法
<mesh position={[1, 2, 3]} />
// 或对复杂对象使用useMemo
const position = useMemo(() => new THREE.Vector3(1, 2, 3), [])
<mesh position={position} />❌ Pitfall 3: Not Using useLoader Cache
❌ 陷阱3: 未使用useLoader缓存
jsx
// ❌ BAD: Loads texture every render
function Component() {
const [texture, setTexture] = useState()
useEffect(() => {
new TextureLoader().load('/texture.jpg', setTexture)
}, [])
return texture ? <meshBasicMaterial map={texture} /> : null
}✅ Solution: Use useLoader (automatic caching)
jsx
// ✅ GOOD: Cached and reused
function Component() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return <meshBasicMaterial map={texture} />
}jsx
// ❌ 错误:每次渲染都加载纹理
function Component() {
const [texture, setTexture] = useState()
useEffect(() => {
new TextureLoader().load('/texture.jpg', setTexture)
}, [])
return texture ? <meshBasicMaterial map={texture} /> : null
}✅ 解决方案: 使用useLoader(自动缓存)
jsx
// ✅ 正确:缓存并复用
function Component() {
const texture = useLoader(TextureLoader, '/texture.jpg')
return <meshBasicMaterial map={texture} />
}❌ Pitfall 4: Conditional Mounting (Expensive)
❌ 陷阱4: 条件挂载(性能开销大)
jsx
// ❌ BAD: Unmounts and remounts (expensive)
{stage === 1 && <Stage1 />}
{stage === 2 && <Stage2 />}
{stage === 3 && <Stage3 />}✅ Solution: Use visibility prop
jsx
// ✅ GOOD: Components stay mounted, just hidden
<Stage1 visible={stage === 1} />
<Stage2 visible={stage === 2} />
<Stage3 visible={stage === 3} />
function Stage1({ visible, ...props }) {
return <group {...props} visible={visible}>...</group>
}jsx
// ❌ 错误:卸载并重新挂载(开销大)
{stage === 1 && <Stage1 />}
{stage === 2 && <Stage2 />}
{stage === 3 && <Stage3 />}✅ 解决方案: 使用visibility属性
jsx
// ✅ 正确:组件保持挂载,仅隐藏
<Stage1 visible={stage === 1} />
<Stage2 visible={stage === 2} />
<Stage3 visible={stage === 3} />
function Stage1({ visible, ...props }) {
return <group {...props} visible={visible}>...</group>
}❌ Pitfall 5: useThree Outside Canvas
❌ 陷阱5: 在Canvas外部使用useThree
jsx
// ❌ BAD: Crashes - useThree must be inside Canvas
function App() {
const { size } = useThree()
return <Canvas>...</Canvas>
}✅ Solution: Use hooks inside Canvas children
jsx
// ✅ GOOD: useThree inside Canvas child
function CameraInfo() {
const { size } = useThree()
return null
}
function App() {
return (
<Canvas>
<CameraInfo />
</Canvas>
)
}jsx
// ❌ 错误:崩溃 - useThree必须在Canvas内部使用
function App() {
const { size } = useThree()
return <Canvas>...</Canvas>
}✅ 解决方案: 在Canvas子组件中使用钩子
jsx
// ✅ 正确:在Canvas子组件中使用useThree
function CameraInfo() {
const { size } = useThree()
return null
}
function App() {
return (
<Canvas>
<CameraInfo />
</Canvas>
)
}❌ Pitfall 6: Not Disposing Resources
❌ 陷阱6: 未释放资源
jsx
// ❌ BAD: Memory leak - textures not disposed
const texture = useLoader(TextureLoader, '/texture.jpg')✅ Solution: R3F handles disposal automatically, but be careful with manual Three.js objects
jsx
// ✅ GOOD: Manual cleanup when needed
useEffect(() => {
const geometry = new THREE.SphereGeometry(1)
const material = new THREE.MeshBasicMaterial()
return () => {
geometry.dispose()
material.dispose()
}
}, [])jsx
// ❌ 错误:内存泄漏 - 纹理未释放
const texture = useLoader(TextureLoader, '/texture.jpg')✅ 解决方案: R3F会自动处理资源释放,但手动创建的Three.js对象需要注意
jsx
// ✅ 正确:必要时手动清理
useEffect(() => {
const geometry = new THREE.SphereGeometry(1)
const material = new THREE.MeshBasicMaterial()
return () => {
geometry.dispose()
material.dispose()
}
}, [])Best Practices
最佳实践
1. Component Composition
1. 组件组合
Break scenes into reusable components:
jsx
function Lights() {
return (
<>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} />
</>
)
}
function Scene() {
return (
<>
<Lights />
<Model />
<Ground />
<Effects />
</>
)
}
<Canvas>
<Scene />
</Canvas>将场景拆分为可复用组件:
jsx
function Lights() {
return (
<>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} />
</>
)
}
function Scene() {
return (
<>
<Lights />
<Model />
<Ground />
<Effects />
</>
)
}
<Canvas>
<Scene />
</Canvas>2. Suspend Heavy Assets
2. 挂起重型资源
Always wrap async operations in Suspense:
jsx
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
<Environment />
</Suspense>
</Canvas>始终将异步操作包裹在Suspense中:
jsx
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
<Environment />
</Suspense>
</Canvas>3. Use TypeScript
3. 使用TypeScript
typescript
import { ThreeElements } from '@react-three/fiber'
function Box(props: ThreeElements['mesh']) {
return (
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}typescript
import { ThreeElements } from '@react-three/fiber'
function Box(props: ThreeElements['mesh']) {
return (
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}4. Organize by Feature
4. 按功能组织代码
src/
components/
3d/
Scene.tsx
Lights.tsx
Camera.tsx
models/
Robot.tsx
Character.tsx
effects/
PostProcessing.tsxsrc/
components/
3d/
Scene.tsx
Lights.tsx
Camera.tsx
models/
Robot.tsx
Character.tsx
effects/
PostProcessing.tsx5. Test with React DevTools Profiler
5. 使用React DevTools性能分析器测试
Monitor re-renders and optimize components causing performance issues.
监控重渲染情况,优化导致性能问题的组件。
Resources
资源
References
参考文档
- - Complete R3F & Drei API documentation
references/api_reference.md - - Detailed hooks usage and patterns
references/hooks_guide.md - - Comprehensive Drei library guide
references/drei_helpers.md
- - 完整的R3F & Drei API文档
references/api_reference.md - - 钩子用法与模式详解
references/hooks_guide.md - - Drei库综合指南
references/drei_helpers.md
Scripts
脚本
- - Generate R3F component boilerplate
scripts/component_generator.py - - Initialize R3F scene with common patterns
scripts/scene_setup.py
- - 生成R3F组件模板
scripts/component_generator.py - - 使用常见模式初始化R3F场景
scripts/scene_setup.py
Assets
资源文件
- - Complete R3F + Vite starter template
assets/starter_r3f/ - - Real-world R3F component examples
assets/examples/
- - 完整的R3F + Vite启动模板
assets/starter_r3f/ - - 真实场景下的R3F组件示例
assets/examples/
External Resources
外部资源
- Official Docs
- Drei Docs
- Three.js Docs
- R3F Discord
- Poimandres (pmnd.rs) - Ecosystem overview
- 官方文档
- Drei文档
- Three.js文档
- R3F社区
- Poimandres (pmnd.rs) - 生态系统概览