mcpField
T
threejs-impl-react-three-fiber

threejs-impl-react-three-fiber

github.com/impertio-studio开发23147 次安装30 周前更新
代码仓库

关于 threejs-impl-react-three-fiber

面向 React Three Fiber 开发者的实现参考技能,介绍 Canvas 配置、Hooks、事件和 JSX 到 Three.js 场景图的映射。还涵盖渲染循环、资源生命周期与性能实践。

主要功能

  1. 01Canvas 渲染器与相机配置
  2. 02useFrame、useThree 等 Hooks
  3. 03JSX 到 Three.js 场景映射
  4. 04指针事件与射线检测
  5. 05渲染循环和资源性能实践

适用场景

  1. 01配置 R3F Canvas 与渲染策略
  2. 02编写逐帧动画和场景状态逻辑
  3. 03处理网格上的指针交互
  4. 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 外,安装时一起下载的参考资料和脚本。

.gitkeep
references/
anti-patterns.md
examples.md
methods.md

详细介绍

threejs-impl-react-three-fiber

快速参考

Canvas 组件属性
属性类型默认值用途
glRenderer props | (canvas) => Renderer{}WebGL 渲染器配置或工厂函数
cameraCamera props | THREE.Camera{ fov: 75, near: 0.1, far: 1000, position: [0,0,5] }默认摄像机
sceneScene props | THREE.Scene{}场景配置
shadowsboolean | ShadowMapTypefalse启用阴影贴图
raycasterRaycaster props{}光线投射器配置
frameloop"always" | "demand" | "never""always"渲染循环策略
resizeResizeOptions{ scroll: true, debounce: { scroll: 50, resize: 0 } }调整大小行为
orthographicbooleanfalse使用正交摄像机
dprnumber | [min, max][1, 2]设备像素比
linearbooleanfalse线性色彩空间
flatbooleanfalse禁用色调映射
legacybooleanfalse禁用色彩管理
eventsEventManagerR3F 默认值自定义事件管理器
eventSourceHTMLElement | React.RefObject父节点事件捕获元素
eventPrefixstring"offset"坐标前缀
onCreated(state: RootState) => void--初始化后回调
onPointerMissed(event: PointerEvent) => void--点击未命中任何网格时触发
fallbackReact.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 场景图操作:

  1. 小写 JSX = Three.js 类。 <mesh /> 会创建 new THREE.Mesh()。<meshStandardMaterial /> 会创建 new THREE.MeshStandardMaterial()。

  2. args = 构造函数参数(数组)。 <sphereGeometry args={[1, 32, 32]} /> 会变为 new THREE.SphereGeometry(1, 32, 32)。当 args 发生变化时,对象会被销毁并重新创建。

  3. attach = 父级属性绑定。 <meshStandardMaterial attach="material" /> 会设置 parent.material = this。几何体会自动附加到 "geometry",材质会自动附加到 "material"。

  4. 使用连字符表示法的 attach 可指定嵌套路径。 attach="shadow-camera" 会设置 parent.shadow.camera = this。数组索引:attach="material-0"。

  5. 函数式 attach。 attach={(parent, self) => { parent.add(self); return () => parent.remove(self); }} 用于自定义绑定/解绑。

  6. 带 .set() 的属性支持简写。 position={[1, 2, 3]} 会调用 object.position.set(1, 2, 3)。color="hotpink" 会调用 object.color.set("hotpink")。

  7. 标量简写。 scale={2} 会调用 object.scale.setScalar(2)。

  8. 连字符格式可访问嵌套属性。 rotation-x={Math.PI} 会设置 object.rotation.x = Math.PI。


Hooks

useFrame
useFrame((state: RootState, delta: number, xrFrame?: XRFrame) => void, priority?: number)

将回调订阅到渲染循环。每帧执行一次。

状态对象的关键属性:

属性类型描述
glTHREE.WebGLRenderer渲染器
sceneTHREE.Scene场景
cameraTHREE.Camera当前相机
clockTHREE.Clock系统时钟
pointerTHREE.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对象接收新属性
事件对象属性
属性类型描述
objectTHREE.Object3D实际命中的网格
eventObjectTHREE.Object3D具有事件处理程序的对象
pointTHREE.Vector3世界空间中的交点
distancenumber相机到交点的距离
uvTHREE.Vector2交点处的 UV 坐标
faceTHREE.Face相交的面
rayTHREE.Ray用于求交的射线
cameraTHREE.Camera当前活动相机
intersectionsIntersection[]所有相交对象
deltanumber从按下到抬起的像素距离
sourceEventEvent原始 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,每次渲染都会创建一个新对象。


参考链接

官方来源

评论

支持 Markdown