3D 组件复用为何总踩坑

Three.js 写组件易,复用难:封装好了跨项目就崩,参数一改就变形。MengTo/threeui 给出的解法,值得每个前端看一眼。

源仓库: MengTo/threeui

一、MengTo/threeui 是什么,为什么一周内冲到 1635 star

threeui 是 Meng To(Design+Code 作者)开源的 ThreeUI 社区目录,目标只有一个:把 Three.js 里零散的 live interactive 组件沉淀成可复用、可拼接、可改参数的”乐高积木”。它不是另一个 three.js starter,也不是封装好的 GUI 库,而是一个组件目录(catalog)——你 clone 下来,看到的是一个能直接 npm run dev 跑起来的站点,每个组件都是独立页面,源码在 src/content/components/* 下,参数改完实时刷新。

它火在两个节点:一是 Three.js 在 2026 年终于把 WebGPU 默认开起来,老的 GUI 写法明显跟不上;二是 r/ClaudeAI 上有人贴出一张”用 threeui + Claude 改 5 行参数就出新场景”的截图,让前端圈开始讨论”3D 组件库是不是也能像 shadcn 那样复制粘贴”。这周 star 一周内翻倍,主要增量来自 r/ClaudeAI 和 HN show 的二次传播。


二、问题 1:把 Three.js 场景”组件化”为什么会变形

很多人在自己项目里封装过一个 <RotatingCube />,第一次跑很顺,搬到另一个项目里灯光、相机、size 全乱。这是 Three.js 旧 GUI 思路的典型坑:实例参数写死在组件外,复用时上下文变了,组件就崩。

threeui 的解法是把所有”外部依赖”(相机、renderer、scene、控件)抽进一个 Stage Provider,组件只读 context:

// src/components/Stage.tsx
import { createContext, useContext, useEffect } from 'react'
import * as THREE from 'three'

const StageContext = createContext<THREE.Scene | null>(null)

export function Stage({ children }: { children: React.ReactNode }) {
  const scene = new THREE.Scene()
  useEffect(() => {
    const dir = new THREE.DirectionalLight(0xffffff, 1)
    dir.position.set(5, 5, 5)
    scene.add(dir)
  }, [scene])
  return <StageContext.Provider value={scene}>{children}</StageContext.Provider>
}

export const useScene = () => {
  const ctx = useContext(StageContext)
  if (!ctx) throw new Error('useScene must be used inside <Stage>')
  return ctx
}

这样每个组件只关心”自己往 scene 里 add 什么”,不关心相机位置、不关心 canvas size。问题就出在没有 Provider 的封装——一旦被外部传参,就一定会变形。


三、问题 2:参数调来调去,怎么知道”哪个 knob 控制了什么”

设计师提需求时最爱说:“灯再亮一点,字再大一点,球再圆一点”。传统做法是改源码、HMR、等 3 秒、看一眼、再改。如果一个组件有 12 个可调参数,能把人调到崩溃。

threeui 的做法是用 Leva 或自建 panel,把组件参数全部外置,并在文档页里直接展示:

// src/content/components/GlowingSphere.tsx
import { useControls } from 'leva'
import { useScene } from '@/components/Stage'

export default function GlowingSphere() {
  const scene = useScene()
  const { radius, color, intensity } = useControls('Sphere', {
    radius: { value: 1, min: 0.1, max: 3, step: 0.1 },
    color: '#ff8800',
    intensity: { value: 2, min: 0, max: 10 },
  })

  // ... useEffect 中根据参数创建 mesh 并 add 到 scene
  return null
}

useControls 会在右侧自动渲染一个可拖拽面板,每个参数的 name 就是它在文档页里高亮显示的”标签”。设计师不需要懂代码,也能看出”哦,原来 intensity 控的是发光强度”。这是 threeui 比一个简单的 starter 高明的地方——它把”组件文档”和”组件参数面板”同一个 source of truth


四、问题 3:组件太多,怎么避免互相打架

经典场景:同一个 scene 里放一个 ParticleField 和一个 WobbleCube,两个都加了 requestAnimationFrame 更新位置。结果就是帧率掉到 20,因为谁都不知道对方在改 transform。

threeui 用的是 React 的 useFrame(来自 @react-three/fiber)统一调度:

import { useFrame } from '@react-three/fiber'

export default function WobbleCube() {
  useFrame((state) => {
    state.scene.children.forEach((c) => {
      if (c.name === 'wobble-cube') {
        c.rotation.x = state.clock.elapsedTime * 0.4
        c.rotation.y = state.clock.elapsedTime * 0.3
      }
    })
  })
  return (
    <mesh name="wobble-cube">
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="hotpink" />
    </mesh>
  )
}

所有组件共用一个 rAF 循环,谁也不单独开 setInterval。多个组件想互相影响,只需要约定好 name,例如 wobble-cube 这个 mesh 的 transform 被 ParticleField 拿来做位置采样,完全不用直接 import 对方。


五、问题 4:怎么让组件能被”复制粘贴”而不是”安装”

shadcn/ui 当年火起来是因为它让你 npx shadcn add button 后真的把源码丢进你项目,三年过去了,你也不用担心升级破坏你的设计。threeui 想做的就是同样的事——不发布 npm 包,只发布组件源码

目录结构长这样:

threeui/
├── src/
│   ├── components/
│   │   ├── Stage.tsx        # 共享 Provider
│   │   └── LevaPanel.tsx    # 共享参数面板
│   └── content/
│       └── components/
│           ├── GlowingSphere.tsx
│           ├── WobbleCube.tsx
│           └── ParticleField.tsx

你想用 GlowingSphere,就把那个文件复制到自己项目,按需补上 useScene / useFrame 两个 hook 即可。这种**“目录即文档”**的策略,对 3D 场景特别重要——因为每个项目的相机、灯光、tone mapping 都不同,强行装包反而会强行覆盖你的设置。


六、小结:它适合谁

如果你的项目是 SaaS 后台、产品官网这类对 Three.js 只是点缀的场景,threeui 大材小用;但只要你的项目有 3D demo 页面、有产品配置器、有数据可视化里要做旋转地球这种持续维护 3D 组件的需求,threeui 给你的是一套”代码 + 文档 + 参数面板”三位一体的样板,比你从零写快三天。

跑起来的成本极低:

git clone https://github.com/MengTo/threeui.git
cd threeui
npm install
npm run dev

默认会在 localhost:3000 打开,左侧是组件列表,点任意一项右侧就是可交互 demo 加上 Leva 控制面板。下一步建议直接 fork,把你自己项目里那个最常复用的 3D 组件,按它的目录结构加进去——比从头写文档快得多。


Sources

本文整理自 GitHub Trending 2026-08-23 榜单、Meng To 在 Design+Code 频道对 threeui 的介绍视频,以及 r/ClaudeAI 上关于”3D 组件复用是否该学 shadcn”的讨论帖(4 天内 327 条回复),相关 HN show 也已二次传播。