react-three-fiber

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React 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
<Canvas>
component sets up a Three.js scene, camera, renderer, and render loop.
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
    - Camera configuration (position, fov, near, far)
  • gl
    - WebGL renderer settings
  • dpr
    - Device pixel ratio (default: [1, 2])
  • shadows
    - Enable shadow mapping (default: false)
  • frameloop
    - "always" (default), "demand", or "never"
  • flat
    - Disable color management for simpler colors
  • linear
    - Use linear color space instead of sRGB
<Canvas>
组件用于设置Three.js场景、相机、渲染器和渲染循环。
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
    - 相机配置(位置、视场角、近裁面、远裁面)
  • gl
    - WebGL渲染器设置
  • dpr
    - 设备像素比(默认值:[1, 2])
  • shadows
    - 启用阴影映射(默认值:false)
  • frameloop
    - 取值为"always"(默认)、"demand"或"never"
  • flat
    - 禁用色彩管理以简化颜色显示
  • linear
    - 使用线性色彩空间而非sRGB

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:
  • position
    object.position.set(x, y, z)
  • rotation
    object.rotation.set(x, y, z)
  • scale
    object.scale.set(x, y, z)
  • args
    → Constructor arguments for geometry/material
  • attach
    → Attach to parent property (e.g.,
    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>
属性映射:
  • position
    object.position.set(x, y, z)
  • rotation
    object.rotation.set(x, y, z)
  • scale
    object.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:
  • state
    - Scene state (camera, scene, gl, clock, etc.)
  • delta
    - Time since last frame (for frame-rate independence)
  • xrFrame
    - XR frame data (for VR/AR)
Important: Never use
setState
inside
useFrame
- it causes unnecessary re-renders!
在每一帧执行代码(动画循环):
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参数:
  • state
    - 场景状态(相机、场景、gl、时钟等)
  • delta
    - 自上一帧以来的时间(确保帧率无关性)
  • xrFrame
    - XR帧数据(用于VR/AR)
重要提示: 绝不要在
useFrame
内使用
setState
——这会导致不必要的重渲染!

4. 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:
  • camera
    - Default camera
  • scene
    - Three.js scene
  • gl
    - WebGL renderer
  • size
    - Canvas dimensions
  • viewport
    - Viewport dimensions in 3D units
  • clock
    - Three.js clock
  • pointer
    - Normalized mouse coordinates
  • invalidate()
    - Manually trigger render
  • setSize()
    - Manually resize canvas
访问场景状态和方法:
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
    - 默认相机
  • scene
    - Three.js场景
  • gl
    - WebGL渲染器
  • size
    - Canvas尺寸
  • viewport
    - 3D单位下的视口尺寸
  • clock
    - Three.js时钟
  • pointer
    - 归一化鼠标坐标
  • invalidate()
    - 手动触发渲染
  • setSize()
    - 手动调整Canvas尺寸

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
<instancedMesh>
for rendering many identical objects:
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
useThree
selectors to avoid unnecessary re-renders:
jsx
// ❌ 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)

使用
useThree
选择器避免不必要的重渲染:
jsx
// ❌ 任何状态变化都会触发重渲染
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.tsx
src/
  components/
    3d/
      Scene.tsx
      Lights.tsx
      Camera.tsx
    models/
      Robot.tsx
      Character.tsx
    effects/
      PostProcessing.tsx

5. Test with React DevTools Profiler

5. 使用React DevTools性能分析器测试

Monitor re-renders and optimize components causing performance issues.

监控重渲染情况,优化导致性能问题的组件。

Resources

资源

References

参考文档

  • references/api_reference.md
    - Complete R3F & Drei API documentation
  • references/hooks_guide.md
    - Detailed hooks usage and patterns
  • references/drei_helpers.md
    - Comprehensive Drei library guide
  • references/api_reference.md
    - 完整的R3F & Drei API文档
  • references/hooks_guide.md
    - 钩子用法与模式详解
  • references/drei_helpers.md
    - Drei库综合指南

Scripts

脚本

  • scripts/component_generator.py
    - Generate R3F component boilerplate
  • scripts/scene_setup.py
    - Initialize R3F scene with common patterns
  • scripts/component_generator.py
    - 生成R3F组件模板
  • scripts/scene_setup.py
    - 使用常见模式初始化R3F场景

Assets

资源文件

  • assets/starter_r3f/
    - Complete R3F + Vite starter template
  • assets/examples/
    - Real-world R3F component examples
  • assets/starter_r3f/
    - 完整的R3F + Vite启动模板
  • assets/examples/
    - 真实场景下的R3F组件示例

External Resources

外部资源