关于 threejs-impl-react-three-fiber
面向 React Three Fiber 开发者的实现参考技能,介绍 Canvas 配置、Hooks、事件和 JSX 到 Three.js 场景图的映射。还涵盖渲染循环、资源生命周期与性能实践。
主要功能
- 01Canvas 渲染器与相机配置
- 02useFrame、useThree 等 Hooks
- 03JSX 到 Three.js 场景映射
- 04指针事件与射线检测
- 05渲染循环和资源性能实践
适用场景
- 01配置 R3F Canvas 与渲染策略
- 02编写逐帧动画和场景状态逻辑
- 03处理网格上的指针交互
- 04减少静态场景渲染与重复分配
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ impertio-studio/ three.js-claude-skill-package --skill threejs-impl-react-three-fiber安装后在 Claude Code 中输入/threejs-impl-react-three-fiber确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitkeepreferences/anti-patterns.mdexamples.mdmethods.md详细介绍
threejs-impl-react-three-fiber
快速参考
Canvas 组件属性
| 属性 | 类型 | 默认值 | 用途 |
|---|---|---|---|
gl | Renderer props | (canvas) => Renderer | {} | WebGL 渲染器配置或工厂函数 |
camera | Camera props | THREE.Camera | { fov: 75, near: 0.1, far: 1000, position: [0,0,5] } | 默认摄像机 |
scene | Scene props | THREE.Scene | {} | 场景配置 |
shadows | boolean | ShadowMapType | false | 启用阴影贴图 |
raycaster | Raycaster props | {} | 光线投射器配置 |
frameloop | "always" | "demand" | "never" | "always" | 渲染循环策略 |
resize | ResizeOptions | { scroll: true, debounce: { scroll: 50, resize: 0 } } | 调整大小行为 |
orthographic | boolean | false | 使用正交摄像机 |
dpr | number | [min, max] | [1, 2] | 设备像素比 |
linear | boolean | false | 线性色彩空间 |
flat | boolean | false | 禁用色调映射 |
legacy | boolean | false | 禁用色彩管理 |
events | EventManager | R3F 默认值 | 自定义事件管理器 |
eventSource | HTMLElement | React.RefObject | 父节点 | 事件捕获元素 |
eventPrefix | string | "offset" | 坐标前缀 |
onCreated | (state: RootState) => void | -- | 初始化后回调 |
onPointerMissed | (event: PointerEvent) => void | -- | 点击未命中任何网格时触发 |
fallback | React.ReactNode | -- | 初始化期间的 DOM 回退内容 |
帧循环模式
| 模式 | 行为 |
|---|---|
"always" | 始终通过 requestAnimationFrame 渲染每一帧 |
"demand" | 仅在调用 invalidate() 时渲染——适用于静态场景 |
"never" | 从不自动渲染——调用方必须调用 advance(timestamp) |
重要警告
绝不要在 useFrame 中创建 Three.js 对象——这会在每一帧分配内存并增加垃圾回收压力。务必在回调外创建对象,或使用 useMemo。
使用 useLoader 时,绝不要忘记 <Suspense>——组件会进入挂起状态,没有 Suspense 边界时将导致崩溃。
绝不要通过 <primitive> 将同一个 Three.js 对象实例多次添加到场景树中——Three.js 对象只能有一个父对象。
只需要一个属性时,绝不要在没有选择器的情况下使用 useThree()——完整状态对象会在每一帧触发重新渲染。务必使用 useThree((s) => s.camera)。
绝不要将命令式的 scene.add() 调用与 R3F 的声明式 JSX 树混用——R3F 会管理场景图,命令式修改会导致不同步。
务必使用 useFrame 提供的 delta 进行动画处理——硬编码时间步长会导致不同帧率下的速度不一致。
务必对命令式创建的几何体和材质使用 useMemo——否则每次渲染都会分配新对象。
JSX 到 Three.js 的映射规则
R3F 使用确定性约定将 JSX 转换为 Three.js 场景图操作:
-
小写 JSX = Three.js 类。
<mesh />会创建new THREE.Mesh()。<meshStandardMaterial />会创建new THREE.MeshStandardMaterial()。 -
args= 构造函数参数(数组)。<sphereGeometry args={[1, 32, 32]} />会变为new THREE.SphereGeometry(1, 32, 32)。当args发生变化时,对象会被销毁并重新创建。 -
attach= 父级属性绑定。<meshStandardMaterial attach="material" />会设置parent.material = this。几何体会自动附加到"geometry",材质会自动附加到"material"。 -
使用连字符表示法的 attach 可指定嵌套路径。
attach="shadow-camera"会设置parent.shadow.camera = this。数组索引:attach="material-0"。 -
函数式 attach。
attach={(parent, self) => { parent.add(self); return () => parent.remove(self); }}用于自定义绑定/解绑。 -
带
.set()的属性支持简写。position={[1, 2, 3]}会调用object.position.set(1, 2, 3)。color="hotpink"会调用object.color.set("hotpink")。 -
标量简写。
scale={2}会调用object.scale.setScalar(2)。 -
连字符格式可访问嵌套属性。
rotation-x={Math.PI}会设置object.rotation.x = Math.PI。
Hooks
useFrame
useFrame((state: RootState, delta: number, xrFrame?: XRFrame) => void, priority?: number)
将回调订阅到渲染循环。每帧执行一次。
状态对象的关键属性:
| 属性 | 类型 | 描述 |
|---|---|---|
gl | THREE.WebGLRenderer | 渲染器 |
scene | THREE.Scene | 场景 |
camera | THREE.Camera | 当前相机 |
clock | THREE.Clock | 系统时钟 |
pointer | THREE.Vector2 | 归一化指针(-1 到 +1) |
size | { width, height, top, left } | 画布尺寸(px) |
viewport | { width, height, factor, distance, aspect } | 相机相对指标 |
invalidate | () => void | 在按需模式下请求渲染 |
advance | (timestamp: number) => void | 在 never 模式下推进一个时刻 |
performance | { current, min, max, regress() } | 自适应性能 |
set | (state) => void | 直接修改状态 |
get | () => RootState | 以非响应式方式读取状态 |
**优先级系统:**回调按优先级从低到高执行。当任意回调的优先级大于 0 时,R3F 会禁用自动 renderer.render()。优先级最高的订阅者必须手动调用 state.gl.render(state.scene, state.camera)。负优先级不会禁用自动渲染。
useThree
const state = useThree() // full state (re-renders often)
const camera = useThree((state) => state.camera) // selector (re-renders only on change)
返回 RootState(与 useFrame 的状态对象相同)。仅需一个属性时,始终使用选择器。
useLoader
const result = useLoader(LoaderClass, url, extensions?, onProgress?)
const results = useLoader(LoaderClass, [url1, url2], extensions?)
基于 Suspense 的资源加载。始终使用 <Suspense fallback={...}> 包裹。
- 资源按 URL 缓存——两次加载相同 URL 会返回缓存结果。
useLoader.preload(LoaderClass, url)会在组件挂载前预加载。- GLTF 结果包含
{ nodes, materials, scene, animations }。
useGraph
const { nodes, materials } = useGraph(object3D)
遍历 Object3D 层级结构,并返回按名称索引的记忆化 { nodes, materials } 集合。
Primitives 和 extend()
Primitives
将现有 Three.js 对象插入声明式树中:
<primitive object={existingMesh} position={[10, 0, 0]} />
- 绝不要多次添加同一个对象实例——Three.js 对象只能有一个父级。
- Primitives 不会自动释放资源;调用方必须管理其生命周期。
extend()
将自定义 Three.js 类注册为 JSX 元素:
import { extend } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
extend({ OrbitControls })
// Now usable as <orbitControls args={[camera, domElement]} />
JSX 元素名称是已注册键对应的驼峰格式。
事件系统
R3F 通过射线投射实现指针事件。事件会沿场景图冒泡。
支持的事件
| 事件 | 触发条件 |
|---|---|
onClick | 单击网格 |
onContextMenu | 右键单击/打开上下文菜单 |
onDoubleClick | 双击 |
onPointerUp | 指针释放 |
onPointerDown | 指针按下 |
onPointerOver | 指针进入网格(持续触发) |
onPointerOut | 指针离开网格 |
onPointerEnter | 指针进入网格(仅触发一次) |
onPointerLeave | 指针离开网格(仅触发一次) |
onPointerMove | 指针在网格上移动 |
onPointerMissed | 点击未命中任何网格(Canvas 级别) |
onWheel | 滚动滚轮 |
onUpdate | 对象接收新属性 |
事件对象属性
| 属性 | 类型 | 描述 |
|---|---|---|
object | THREE.Object3D | 实际命中的网格 |
eventObject | THREE.Object3D | 具有事件处理程序的对象 |
point | THREE.Vector3 | 世界空间中的交点 |
distance | number | 相机到交点的距离 |
uv | THREE.Vector2 | 交点处的 UV 坐标 |
face | THREE.Face | 相交的面 |
ray | THREE.Ray | 用于求交的射线 |
camera | THREE.Camera | 当前活动相机 |
intersections | Intersection[] | 所有相交对象 |
delta | number | 从按下到抬起的像素距离 |
sourceEvent | Event | 原始 DOM 事件 |
stopPropagation() | function | 阻止事件向被遮挡对象冒泡 |
性能模式
释放资源
- 当组件卸载时,R3F 始终会对 Three.js 对象调用
dispose(),以释放 GPU 资源。 - 在元素上设置
dispose={null}可阻止自动释放——适用于多个组件共享对象的情况。
静态场景
对于不会持续动画的场景(仪表盘、配置器),使用 frameloop="demand" 和 invalidate()。这样可以节省 GPU 运算周期。
传送门
import { createPortal } from '@react-three/fiber'
createPortal(children, targetScene)
将子元素渲染到不同的场景/层中,而不影响主场景图。
对命令式对象使用 useMemo
始终使用 useMemo 包装以命令式方式创建的几何体和材质:
const geometry = useMemo(() => new THREE.TorusKnotGeometry(1, 0.3, 128, 32), [])
如果不使用 useMemo,每次渲染都会创建一个新对象。
参考链接
- references/methods.md —— Hook 签名和 Canvas API
- references/examples.md —— 可运行的 R3F 代码示例
- references/anti-patterns.md —— 使用 R3F 时应避免的做法