r3f-fundamentals
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Three Fiber Fundamentals
React Three Fiber 核心基础
Quick Start
快速开始
tsx
import { Canvas } from '@react-three/fiber'
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
})
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
)
}
export default function App() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<RotatingBox />
</Canvas>
)
}tsx
import { Canvas } from '@react-three/fiber'
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
function RotatingBox() {
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.rotation.x += delta
meshRef.current.rotation.y += delta * 0.5
})
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
)
}
export default function App() {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<RotatingBox />
</Canvas>
)
}Canvas Component
Canvas 组件
The root component that creates the WebGL context, scene, camera, and renderer.
tsx
import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
// Camera configuration
camera={{
position: [0, 5, 10],
fov: 75,
near: 0.1,
far: 1000,
}}
// Or use orthographic
orthographic
camera={{ zoom: 50, position: [0, 0, 100] }}
// Renderer settings
gl={{
antialias: true,
alpha: true,
powerPreference: 'high-performance',
preserveDrawingBuffer: true, // For screenshots
}}
dpr={[1, 2]} // Pixel ratio min/max
// Shadows
shadows // or shadows="soft" | "basic" | "percentage"
// Color management
flat // Disable automatic sRGB color management
// Frame loop control
frameloop="demand" // 'always' | 'demand' | 'never'
// Event handling
eventSource={document.getElementById('root')}
eventPrefix="client" // 'offset' | 'client' | 'page' | 'layer' | 'screen'
// Callbacks
onCreated={(state) => {
console.log('Canvas ready:', state.gl, state.scene, state.camera)
}}
onPointerMissed={() => console.log('Clicked background')}
// Styling
style={{ width: '100%', height: '100vh' }}
>
<Scene />
</Canvas>
)
}作为根组件,用于创建WebGL上下文、场景、相机和渲染器。
tsx
import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
// 相机配置
camera={{
position: [0, 5, 10],
fov: 75,
near: 0.1,
far: 1000,
}}
// 或使用正交相机
orthographic
camera={{ zoom: 50, position: [0, 0, 100] }}
// 渲染器设置
gl={{
antialias: true,
alpha: true,
powerPreference: 'high-performance',
preserveDrawingBuffer: true, // 用于截图
}}
dpr={[1, 2]} // 像素比例最小值/最大值
// 阴影设置
shadows // 或 shadows="soft" | "basic" | "percentage"
// 色彩管理
flat // 禁用自动sRGB色彩管理
// 帧循环控制
frameloop="demand" // 'always' | 'demand' | 'never'
// 事件处理
eventSource={document.getElementById('root')}
eventPrefix="client" // 'offset' | 'client' | 'page' | 'layer' | 'screen'
// 回调函数
onCreated={(state) => {
console.log('Canvas已就绪:', state.gl, state.scene, state.camera)
}}
onPointerMissed={() => console.log('点击了背景')}
// 样式设置
style={{ width: '100%', height: '100vh' }}
>
<Scene />
</Canvas>
)
}Canvas Defaults
Canvas 默认配置
R3F sets sensible defaults:
- Renderer: antialias, alpha, outputColorSpace = SRGBColorSpace
- Camera: PerspectiveCamera at [0, 0, 5]
- Scene: Automatic resize handling
- Events: Pointer events enabled
R3F提供了合理的默认值:
- 渲染器:开启抗锯齿、透明通道,outputColorSpace = SRGBColorSpace
- 相机:PerspectiveCamera,初始位置[0, 0, 5]
- 场景:自动处理窗口大小调整
- 事件:启用指针事件
useFrame Hook
useFrame 钩子函数
Subscribe to the render loop. Called every frame (typically 60fps).
tsx
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function AnimatedMesh() {
const meshRef = useRef()
useFrame((state, delta, xrFrame) => {
// state: Full R3F state (see useThree)
// delta: Time since last frame in seconds
// xrFrame: XR frame if in VR/AR mode
// Animate rotation
meshRef.current.rotation.y += delta
// Access clock
const elapsed = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(elapsed) * 2
// Access pointer position (-1 to 1)
const { x, y } = state.pointer
meshRef.current.rotation.x = y * 0.5
meshRef.current.rotation.z = x * 0.5
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}订阅渲染循环,每帧都会被调用(通常为60fps)。
tsx
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function AnimatedMesh() {
const meshRef = useRef()
useFrame((state, delta, xrFrame) => {
// state: 完整的R3F状态(参考useThree)
// delta: 自上一帧以来的时间(秒)
// xrFrame: VR/AR模式下的XR帧
// 旋转动画
meshRef.current.rotation.y += delta
// 访问时钟
const elapsed = state.clock.elapsedTime
meshRef.current.position.y = Math.sin(elapsed) * 2
// 访问指针位置(范围-1到1)
const { x, y } = state.pointer
meshRef.current.rotation.x = y * 0.5
meshRef.current.rotation.z = x * 0.5
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}useFrame with Priority
带优先级的 useFrame
Control render order with priority (higher = later).
tsx
// Default priority is 0
useFrame((state, delta) => {
// Runs first
}, -1)
useFrame((state, delta) => {
// Runs after priority -1
}, 0)
// Manual rendering with positive priority
useFrame((state, delta) => {
// Take over rendering
state.gl.render(state.scene, state.camera)
}, 1)通过优先级控制渲染顺序(值越高,执行越晚)。
tsx
// 默认优先级为0
useFrame((state, delta) => {
// 最先执行
}, -1)
useFrame((state, delta) => {
// 在优先级-1之后执行
}, 0)
// 正优先级用于手动渲染
useFrame((state, delta) => {
// 接管渲染流程
state.gl.render(state.scene, state.camera)
}, 1)Conditional useFrame
条件式 useFrame
tsx
function ConditionalAnimation({ active }) {
useFrame((state, delta) => {
if (!active) return // Skip when inactive
meshRef.current.rotation.y += delta
})
}tsx
function ConditionalAnimation({ active }) {
useFrame((state, delta) => {
if (!active) return // 非激活状态时跳过
meshRef.current.rotation.y += delta
})
}useThree Hook
useThree 钩子函数
Access the R3F state store.
tsx
import { useThree } from '@react-three/fiber'
function CameraInfo() {
// Get full state (triggers re-render on any change)
const state = useThree()
// Selective subscription (recommended)
const camera = useThree((state) => state.camera)
const gl = useThree((state) => state.gl)
const scene = useThree((state) => state.scene)
const size = useThree((state) => state.size)
// Available state properties:
// gl: WebGLRenderer
// scene: Scene
// camera: Camera
// raycaster: Raycaster
// pointer: Vector2 (normalized -1 to 1)
// mouse: Vector2 (deprecated, use pointer)
// clock: Clock
// size: { width, height, top, left }
// viewport: { width, height, factor, distance, aspect }
// performance: { current, min, max, debounce, regress }
// events: Event handlers
// set: State setter
// get: State getter
// invalidate: Trigger re-render (for frameloop="demand")
// advance: Advance one frame (for frameloop="never")
return null
}访问R3F的状态存储。
tsx
import { useThree } from '@react-three/fiber'
function CameraInfo() {
// 获取完整状态(任何状态变化都会触发重渲染)
const state = useThree()
// 选择性订阅(推荐使用)
const camera = useThree((state) => state.camera)
const gl = useThree((state) => state.gl)
const scene = useThree((state) => state.scene)
const size = useThree((state) => state.size)
// 可用的状态属性:
// gl: WebGLRenderer
// scene: Scene
// camera: Camera
// raycaster: Raycaster
// pointer: Vector2(归一化值,范围-1到1)
// mouse: Vector2(已废弃,使用pointer替代)
// clock: Clock
// size: { width, height, top, left }
// viewport: { width, height, factor, distance, aspect }
// performance: { current, min, max, debounce, regress }
// events: 事件处理器
// set: 状态设置器
// get: 状态获取器
// invalidate: 触发重渲染(适用于frameloop="demand"模式)
// advance: 推进一帧(适用于frameloop="never"模式)
return null
}Common useThree Patterns
useThree 常见用法
tsx
// Responsive to viewport
function ResponsiveObject() {
const viewport = useThree((state) => state.viewport)
return (
<mesh scale={[viewport.width / 4, viewport.height / 4, 1]}>
<planeGeometry />
<meshBasicMaterial color="blue" />
</mesh>
)
}
// Manual render trigger
function TriggerRender() {
const invalidate = useThree((state) => state.invalidate)
const handleClick = () => {
// Trigger render when using frameloop="demand"
invalidate()
}
}
// Update camera
function CameraController() {
const camera = useThree((state) => state.camera)
const set = useThree((state) => state.set)
useEffect(() => {
camera.position.set(10, 10, 10)
camera.lookAt(0, 0, 0)
}, [camera])
}tsx
// 响应视口变化
function ResponsiveObject() {
const viewport = useThree((state) => state.viewport)
return (
<mesh scale={[viewport.width / 4, viewport.height / 4, 1]}>
<planeGeometry />
<meshBasicMaterial color="blue" />
</mesh>
)
}
// 手动触发渲染
function TriggerRender() {
const invalidate = useThree((state) => state.invalidate)
const handleClick = () => {
// 在frameloop="demand"模式下触发渲染
invalidate()
}
}
// 更新相机
function CameraController() {
const camera = useThree((state) => state.camera)
const set = useThree((state) => state.set)
useEffect(() => {
camera.position.set(10, 10, 10)
camera.lookAt(0, 0, 0)
}, [camera])
}JSX Elements
JSX 元素
All Three.js objects are available as JSX elements (camelCase).
所有Three.js对象都可以通过驼峰式命名的JSX元素使用。
Meshes
网格对象
tsx
// Basic mesh structure
<mesh
position={[0, 0, 0]} // x, y, z
rotation={[0, Math.PI, 0]} // Euler angles in radians
scale={[1, 2, 1]} // x, y, z or single number
visible={true}
castShadow
receiveShadow
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
// With ref
const meshRef = useRef()
<mesh ref={meshRef} />
// meshRef.current is the THREE.Meshtsx
// 基础网格结构
<mesh
position={[0, 0, 0]} // x, y, z 坐标
rotation={[0, Math.PI, 0]} // 欧拉角(弧度)
scale={[1, 2, 1]} // x, y, z 缩放比例或单个数值
visible={true}
castShadow
receiveShadow
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="red" />
</mesh>
// 使用引用
const meshRef = useRef()
<mesh ref={meshRef} />
// meshRef.current 对应的是 THREE.Mesh 实例Geometry args
几何体参数
Constructor arguments via prop:
argstsx
// BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
<boxGeometry args={[1, 1, 1, 1, 1, 1]} />
// SphereGeometry(radius, widthSegments, heightSegments)
<sphereGeometry args={[1, 32, 32]} />
// PlaneGeometry(width, height, widthSegments, heightSegments)
<planeGeometry args={[10, 10]} />
// CylinderGeometry(radiusTop, radiusBottom, height, radialSegments)
<cylinderGeometry args={[1, 1, 2, 32]} />通过属性传递构造函数参数:
argstsx
// BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
<boxGeometry args={[1, 1, 1, 1, 1, 1]} />
// SphereGeometry(radius, widthSegments, heightSegments)
<sphereGeometry args={[1, 32, 32]} />
// PlaneGeometry(width, height, widthSegments, heightSegments)
<planeGeometry args={[10, 10]} />
// CylinderGeometry(radiusTop, radiusBottom, height, radialSegments)
<cylinderGeometry args={[1, 1, 2, 32]} />Groups
组对象
tsx
<group position={[5, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<mesh position={[-1, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="red" />
</mesh>
<mesh position={[1, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="blue" />
</mesh>
</group>tsx
<group position={[5, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<mesh position={[-1, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="red" />
</mesh>
<mesh position={[1, 0, 0]}>
<boxGeometry />
<meshStandardMaterial color="blue" />
</mesh>
</group>Nested Properties
嵌套属性
Use dashes for nested properties:
tsx
<mesh
position-x={5}
rotation-y={Math.PI}
scale-z={2}
>
<meshStandardMaterial
color="red"
metalness={0.8}
roughness={0.2}
/>
</mesh>
// Shadow camera properties
<directionalLight
castShadow
shadow-mapSize={[2048, 2048]}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-camera-top={10}
shadow-camera-bottom={-10}
/>使用短横线连接嵌套属性:
tsx
<mesh
position-x={5}
rotation-y={Math.PI}
scale-z={2}
>
<meshStandardMaterial
color="red"
metalness={0.8}
roughness={0.2}
/>
</mesh>
// 阴影相机属性
<directionalLight
castShadow
shadow-mapSize={[2048, 2048]}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-camera-top={10}
shadow-camera-bottom={-10}
/>attach Prop
attach 属性
Control how children attach to parents:
tsx
<mesh>
<boxGeometry />
{/* Default: attaches as 'material' */}
<meshStandardMaterial />
</mesh>
{/* Explicit attach */}
<mesh>
<boxGeometry attach="geometry" />
<meshStandardMaterial attach="material" />
</mesh>
{/* Array attachment */}
<mesh>
<boxGeometry />
<meshStandardMaterial attach="material-0" color="red" />
<meshStandardMaterial attach="material-1" color="blue" />
</mesh>
{/* Custom attachment with function */}
<someObject>
<texture
attach={(parent, self) => {
parent.map = self
return () => { parent.map = null } // Cleanup
}}
/>
</someObject>控制子元素如何附加到父元素:
tsx
<mesh>
<boxGeometry />
{/* 默认:附加为'material' */}
<meshStandardMaterial />
</mesh>
{/* 显式指定附加目标 */}
<mesh>
<boxGeometry attach="geometry" />
<meshStandardMaterial attach="material" />
</mesh>
{/* 数组形式附加 */}
<mesh>
<boxGeometry />
<meshStandardMaterial attach="material-0" color="red" />
<meshStandardMaterial attach="material-1" color="blue" />
</mesh>
{/* 使用函数自定义附加逻辑 */}
<someObject>
<texture
attach={(parent, self) => {
parent.map = self
return () => { parent.map = null } // 清理函数
}}
/>
</someObject>Event Handling
事件处理
R3F provides React-style events on 3D objects.
tsx
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [clicked, setClicked] = useState(false)
return (
<mesh
onClick={(e) => {
e.stopPropagation() // Prevent bubbling
setClicked(!clicked)
// Event properties:
console.log(e.object) // THREE.Mesh
console.log(e.point) // Vector3 - intersection point
console.log(e.distance) // Distance from camera
console.log(e.face) // Intersected face
console.log(e.faceIndex) // Face index
console.log(e.uv) // UV coordinates
console.log(e.normal) // Face normal
console.log(e.pointer) // Normalized pointer coords
console.log(e.ray) // Raycaster ray
console.log(e.camera) // Camera
console.log(e.delta) // Distance moved (drag events)
}}
onContextMenu={(e) => console.log('Right click')}
onDoubleClick={(e) => console.log('Double click')}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
document.body.style.cursor = 'pointer'
}}
onPointerOut={(e) => {
setHovered(false)
document.body.style.cursor = 'default'
}}
onPointerDown={(e) => console.log('Pointer down')}
onPointerUp={(e) => console.log('Pointer up')}
onPointerMove={(e) => console.log('Moving over mesh')}
onWheel={(e) => console.log('Wheel:', e.deltaY)}
scale={hovered ? 1.2 : 1}
>
<boxGeometry />
<meshStandardMaterial color={clicked ? 'hotpink' : 'orange'} />
</mesh>
)
}R3F为3D对象提供了类React风格的事件。
tsx
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [clicked, setClicked] = useState(false)
return (
<mesh
onClick={(e) => {
e.stopPropagation() // 阻止事件冒泡
setClicked(!clicked)
// 事件属性:
console.log(e.object) // THREE.Mesh 实例
console.log(e.point) // Vector3 - 交点坐标
console.log(e.distance) // 与相机的距离
console.log(e.face) // 被点击的面
console.log(e.faceIndex) // 面的索引
console.log(e.uv) // UV坐标
console.log(e.normal) // 面的法向量
console.log(e.pointer) // 归一化指针坐标
console.log(e.ray) // Raycaster 射线
console.log(e.camera) // 相机实例
console.log(e.delta) // 拖拽距离(仅拖拽事件)
}}
onContextMenu={(e) => console.log('右键点击')}
onDoubleClick={(e) => console.log('双击')}
onPointerOver={(e) => {
e.stopPropagation()
setHovered(true)
document.body.style.cursor = 'pointer'
}}
onPointerOut={(e) => {
setHovered(false)
document.body.style.cursor = 'default'
}}
onPointerDown={(e) => console.log('指针按下')}
onPointerUp={(e) => console.log('指针抬起')}
onPointerMove={(e) => console.log('在网格上移动指针')}
onWheel={(e) => console.log('滚轮滚动:', e.deltaY)}
scale={hovered ? 1.2 : 1}
>
<boxGeometry />
<meshStandardMaterial color={clicked ? 'hotpink' : 'orange'} />
</mesh>
)
}Event Propagation
事件冒泡
Events bubble up through the scene graph:
tsx
<group onClick={(e) => console.log('Group clicked')}>
<mesh onClick={(e) => {
e.stopPropagation() // Stop bubbling to group
console.log('Mesh clicked')
}}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</group>事件会沿着场景图向上冒泡:
tsx
<group onClick={(e) => console.log('组被点击')}>
<mesh onClick={(e) => {
e.stopPropagation() // 阻止事件冒泡到组
console.log('网格被点击')
}}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</group>primitive Element
primitive 元素
Use existing Three.js objects directly:
tsx
import * as THREE from 'three'
// Existing object
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshStandardMaterial({ color: 'red' })
const mesh = new THREE.Mesh(geometry, material)
function Scene() {
return <primitive object={mesh} position={[0, 1, 0]} />
}
// Common with loaded models
function Model({ gltf }) {
return <primitive object={gltf.scene} />
}直接使用已有的Three.js对象:
tsx
import * as THREE from 'three'
// 已创建的对象
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshStandardMaterial({ color: 'red' })
const mesh = new THREE.Mesh(geometry, material)
function Scene() {
return <primitive object={mesh} position={[0, 1, 0]} />
}
// 常用于加载外部模型
function Model({ gltf }) {
return <primitive object={gltf.scene} />
}extend Function
extend 函数
Register custom Three.js classes for JSX use:
tsx
import { extend } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// Extend once (usually at module level)
extend({ OrbitControls })
// Now use as JSX
function Scene() {
const { camera, gl } = useThree()
return <orbitControls args={[camera, gl.domElement]} />
}
// TypeScript declaration
declare global {
namespace JSX {
interface IntrinsicElements {
orbitControls: ReactThreeFiber.Object3DNode<OrbitControls, typeof OrbitControls>
}
}
}注册自定义Three.js类,以便通过JSX使用:
tsx
import { extend } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 扩展一次(通常在模块级别执行)
extend({ OrbitControls })
// 现在可以作为JSX元素使用
function Scene() {
const { camera, gl } = useThree()
return <orbitControls args={[camera, gl.domElement]} />
}
// TypeScript 声明
declare global {
namespace JSX {
interface IntrinsicElements {
orbitControls: ReactThreeFiber.Object3DNode<OrbitControls, typeof OrbitControls>
}
}
}Refs and Imperative Access
引用与命令式访问
tsx
import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function MeshWithRef() {
const meshRef = useRef<THREE.Mesh>(null)
const materialRef = useRef<THREE.MeshStandardMaterial>(null)
useEffect(() => {
if (meshRef.current) {
// Direct Three.js access
meshRef.current.geometry.computeBoundingBox()
console.log(meshRef.current.geometry.boundingBox)
}
}, [])
useFrame(() => {
if (materialRef.current) {
materialRef.current.color.setHSL(Math.random(), 1, 0.5)
}
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial ref={materialRef} />
</mesh>
)
}tsx
import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function MeshWithRef() {
const meshRef = useRef<THREE.Mesh>(null)
const materialRef = useRef<THREE.MeshStandardMaterial>(null)
useEffect(() => {
if (meshRef.current) {
// 直接访问Three.js对象
meshRef.current.geometry.computeBoundingBox()
console.log(meshRef.current.geometry.boundingBox)
}
}, [])
useFrame(() => {
if (materialRef.current) {
materialRef.current.color.setHSL(Math.random(), 1, 0.5)
}
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial ref={materialRef} />
</mesh>
)
}Performance Patterns
性能优化方案
Avoiding Re-renders
避免不必要的重渲染
tsx
// BAD: Creates new object every render
<mesh position={[x, y, z]} />
// GOOD: Mutate existing position
const meshRef = useRef()
useFrame(() => {
meshRef.current.position.x = x
})
<mesh ref={meshRef} />
// GOOD: Use useMemo for static values
const position = useMemo(() => [x, y, z], [x, y, z])
<mesh position={position} />tsx
// 不良写法:每次渲染都会创建新数组
<mesh position={[x, y, z]} />
// 推荐写法:修改已有位置对象
const meshRef = useRef()
useFrame(() => {
meshRef.current.position.x = x
})
<mesh ref={meshRef} />
// 推荐写法:使用useMemo缓存静态值
const position = useMemo(() => [x, y, z], [x, y, z])
<mesh position={position} />Component Isolation
组件隔离
tsx
// Isolate animated components to prevent parent re-renders
function Scene() {
return (
<>
<StaticEnvironment />
<AnimatedObject /> {/* Only this re-renders on animation */}
</>
)
}
function AnimatedObject() {
const ref = useRef()
useFrame((_, delta) => {
ref.current.rotation.y += delta
})
return <mesh ref={ref}><boxGeometry /></mesh>
}tsx
// 隔离动画组件,避免父组件重渲染
function Scene() {
return (
<>
<StaticEnvironment />
<AnimatedObject /> {/* 只有这个组件会在动画时重渲染 */}
</>
)
}
function AnimatedObject() {
const ref = useRef()
useFrame((_, delta) => {
ref.current.rotation.y += delta
})
return <mesh ref={ref}><boxGeometry /></mesh>
}Dispose
资源释放
R3F auto-disposes geometries, materials, and textures. Override with:
tsx
<mesh dispose={null}> {/* Prevent auto-dispose */}
<boxGeometry />
<meshStandardMaterial />
</mesh>R3F会自动释放几何体、材质和纹理资源。可以通过以下方式覆盖默认行为:
tsx
<mesh dispose={null}> {/* 禁止自动释放 */}
<boxGeometry />
<meshStandardMaterial />
</mesh>Common Patterns
常见实现模式
Fullscreen Canvas
全屏Canvas
tsx
// styles.css
html, body, #root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
// App.tsx
<Canvas style={{ width: '100%', height: '100%' }}>tsx
// styles.css
html, body, #root {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
// App.tsx
<Canvas style={{ width: '100%', height: '100%' }}>Responsive Canvas
响应式Canvas
tsx
function ResponsiveScene() {
const { viewport } = useThree()
return (
<mesh scale={Math.min(viewport.width, viewport.height) / 5}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}tsx
function ResponsiveScene() {
const { viewport } = useThree()
return (
<mesh scale={Math.min(viewport.width, viewport.height) / 5}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}Forwarding Refs
转发引用
tsx
import { forwardRef } from 'react'
const CustomMesh = forwardRef((props, ref) => {
return (
<mesh ref={ref} {...props}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
})
// Usage
const meshRef = useRef()
<CustomMesh ref={meshRef} position={[0, 1, 0]} />tsx
import { forwardRef } from 'react'
const CustomMesh = forwardRef((props, ref) => {
return (
<mesh ref={ref} {...props}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
})
// 使用方式
const meshRef = useRef()
<CustomMesh ref={meshRef} position={[0, 1, 0]} />Debugging with Leva
使用Leva进行调试
Leva provides a GUI for tweaking parameters in real-time during development.
Leva提供了一个GUI界面,方便开发时实时调整参数。
Installation
安装
bash
npm install levabash
npm install levaBasic Controls
基础控件
tsx
import { useControls } from 'leva'
function DebugMesh() {
const { position, color, scale, visible } = useControls({
position: { value: [0, 0, 0], step: 0.1 },
color: '#ff0000',
scale: { value: 1, min: 0.1, max: 5, step: 0.1 },
visible: true,
})
return (
<mesh position={position} scale={scale} visible={visible}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}tsx
import { useControls } from 'leva'
function DebugMesh() {
const { position, color, scale, visible } = useControls({
position: { value: [0, 0, 0], step: 0.1 },
color: '#ff0000',
scale: { value: 1, min: 0.1, max: 5, step: 0.1 },
visible: true,
})
return (
<mesh position={position} scale={scale} visible={visible}>
<boxGeometry />
<meshStandardMaterial color={color} />
</mesh>
)
}Organized Folders
分组管理
tsx
import { useControls, folder } from 'leva'
function DebugScene() {
const { lightIntensity, lightColor, shadowMapSize } = useControls({
Lighting: folder({
lightIntensity: { value: 1, min: 0, max: 5 },
lightColor: '#ffffff',
shadowMapSize: { value: 1024, options: [512, 1024, 2048, 4096] },
}),
Camera: folder({
fov: { value: 75, min: 30, max: 120 },
near: { value: 0.1, min: 0.01, max: 1 },
}),
})
return (
<directionalLight
intensity={lightIntensity}
color={lightColor}
shadow-mapSize={[shadowMapSize, shadowMapSize]}
/>
)
}tsx
import { useControls, folder } from 'leva'
function DebugScene() {
const { lightIntensity, lightColor, shadowMapSize } = useControls({
光照: folder({
lightIntensity: { value: 1, min: 0, max: 5 },
lightColor: '#ffffff',
shadowMapSize: { value: 1024, options: [512, 1024, 2048, 4096] },
}),
相机: folder({
fov: { value: 75, min: 30, max: 120 },
near: { value: 0.1, min: 0.01, max: 1 },
}),
})
return (
<directionalLight
intensity={lightIntensity}
color={lightColor}
shadow-mapSize={[shadowMapSize, shadowMapSize]}
/>
)
}Button Actions
按钮操作
tsx
import { useControls, button } from 'leva'
function DebugActions() {
const meshRef = useRef()
useControls({
'Reset Position': button(() => {
meshRef.current.position.set(0, 0, 0)
}),
'Random Color': button(() => {
meshRef.current.material.color.setHex(Math.random() * 0xffffff)
}),
'Log State': button(() => {
console.log(meshRef.current.position)
}),
})
return <mesh ref={meshRef}>...</mesh>
}tsx
import { useControls, button } from 'leva'
function DebugActions() {
const meshRef = useRef()
useControls({
'重置位置': button(() => {
meshRef.current.position.set(0, 0, 0)
}),
'随机颜色': button(() => {
meshRef.current.material.color.setHex(Math.random() * 0xffffff)
}),
'打印状态': button(() => {
console.log(meshRef.current.position)
}),
})
return <mesh ref={meshRef}>...</mesh>
}Hide in Production
生产环境隐藏
tsx
import { Leva } from 'leva'
function App() {
return (
<>
{/* Hide Leva panel in production */}
<Leva hidden={process.env.NODE_ENV === 'production'} />
<Canvas>
<Scene />
</Canvas>
</>
)
}tsx
import { Leva } from 'leva'
function App() {
return (
<>
{/* 生产环境隐藏Leva面板 */}
<Leva hidden={process.env.NODE_ENV === 'production'} />
<Canvas>
<Scene />
</Canvas>
</>
)
}Monitor Values (Read-Only)
监控数值(只读)
tsx
import { useControls, monitor } from 'leva'
import { useFrame } from '@react-three/fiber'
function PerformanceMonitor() {
const [fps, setFps] = useState(0)
useControls({
FPS: monitor(() => fps, { graph: true, interval: 100 }),
})
useFrame((state) => {
// Update FPS display
setFps(Math.round(1 / state.clock.getDelta()))
})
return null
}tsx
import { useControls, monitor } from 'leva'
import { useFrame } from '@react-three/fiber'
function PerformanceMonitor() {
const [fps, setFps] = useState(0)
useControls({
FPS: monitor(() => fps, { graph: true, interval: 100 }),
})
useFrame((state) => {
// 更新FPS显示
setFps(Math.round(1 / state.clock.getDelta()))
})
return null
}Integration with useFrame
与useFrame集成
tsx
function AnimatedDebugMesh() {
const meshRef = useRef()
const { speed, amplitude, enabled } = useControls('Animation', {
enabled: true,
speed: { value: 1, min: 0, max: 5 },
amplitude: { value: 1, min: 0, max: 3 },
})
useFrame(({ clock }) => {
if (!enabled) return
meshRef.current.position.y = Math.sin(clock.elapsedTime * speed) * amplitude
})
return (
<mesh ref={meshRef}>
<sphereGeometry />
<meshStandardMaterial color="cyan" />
</mesh>
)
}tsx
function AnimatedDebugMesh() {
const meshRef = useRef()
const { speed, amplitude, enabled } = useControls('动画', {
enabled: true,
speed: { value: 1, min: 0, max: 5 },
amplitude: { value: 1, min: 0, max: 3 },
})
useFrame(({ clock }) => {
if (!enabled) return
meshRef.current.position.y = Math.sin(clock.elapsedTime * speed) * amplitude
})
return (
<mesh ref={meshRef}>
<sphereGeometry />
<meshStandardMaterial color="cyan" />
</mesh>
)
}See Also
扩展阅读
- - Geometry creation
r3f-geometry - - Material configuration
r3f-materials - - Lights and shadows
r3f-lighting - - Controls and user input
r3f-interaction
- - 几何体创建
r3f-geometry - - 材质配置
r3f-materials - - 灯光与阴影
r3f-lighting - - 控制器与用户输入
r3f-interaction