"当你盯着一个立方体太久,立方体也会开始盯着你。"
------某位迷失在
scene.graph
中的开发者
🎬 前情提要:为什么要封装组件?
Three.js 很强,但原始。它就像一把瑞士军刀,功能丰富但刀锋裸露,如果直接暴露给产品团队------不出一周,就有人能用它造出一个黑洞。
于是我们需要 封装:
- ✅ 更清晰的 API
- ✅ 更安全的默认值
- ✅ 更易组合的组件体系
- ✅ 更接近"产品级"的开发体验
🧩 工程定位:组件库不是 UI 框架,而是 3D 工具箱
别把组件库想成是"3D 的 Vuetify",它更像是:
- 一组可复用的 Three.js 封装单元
- 能组合成场景、交互逻辑、工具控件的积木块
- 面向开发者,不面向最终用户
我们目标是从"裸手搬砖"进化成"模块拼装太空站"。
🧠 底层构建哲学:Three.js 是树,组件库是森林生态
Three.js 的核心抽象是 Object3D
,一切场景节点都继承自它。
你的组件库需要:
- 封装每类 Object3D 实例(如 Mesh、Group、Light)
- 提供通用配置接口(如材质、几何体、交互事件)
- 保持组件之间可组合、可拓展、可继承
🧱 1. 封装基础组件:Mesh 是世界的砖头
arduino
export function createBox({ width = 1, height = 1, depth = 1, color = 0x00ff00 }) {
const geometry = new THREE.BoxGeometry(width, height, depth);
const material = new THREE.MeshStandardMaterial({ color });
return new THREE.Mesh(geometry, material);
}
封装点:
- 入参结构化:更易 IDE 补全
- 默认值温柔合理:产品安全上线
- 输出 Mesh:可直接加入
scene
🔄 2. 拓展材质库:不做一块死板的砖
php
export function createGlowMaterial(baseColor = 0xffff00) {
const material = new THREE.MeshStandardMaterial({
color: baseColor,
emissive: baseColor,
emissiveIntensity: 1.2,
roughness: 0.3,
metalness: 0.6,
});
return material;
}
你可以封装不同风格材质:
- 高光金属风
createChromeMaterial()
- 科技蓝光风
createNeonBlue()
- 手绘 Toon 风
createToonShaderMaterial()
建议统一导出到 /materials/index.js
🧠 3. 拓展场景标准库:一键加天光地面宇宙飞船
ini
export function createDefaultScene() {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x101010);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0);
hemiLight.position.set(0, 20, 0);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(3, 10, 5);
scene.add(hemiLight, dirLight);
return scene;
}
每个产品都需要一套"默认舞台"。
就像芭蕾舞者不可能光脚跳舞一样,Three.js 对象也需要被放进合适的"剧场"。
🔧 4. 封装控制器与辅助器:三维空间的 UI 工具箱
ini
export function attachOrbitControls(camera, domElement) {
const controls = new OrbitControls(camera, domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
return controls;
}
或例如封装一个坐标轴辅助器:
arduino
export function createAxes(size = 5) {
return new THREE.AxesHelper(size);
}
这些都可以打包进 /controls/index.js
,方便调用。
🧪 5. 组件拼装范式:组合而非继承
ini
export function createInteractiveBox(scene, camera, renderer) {
const box = createBox({ color: 0xff4444 });
scene.add(box);
const controls = attachOrbitControls(camera, renderer.domElement);
const axes = createAxes();
scene.add(axes);
return { box, controls, axes };
}
封装组件不要执念于"面向对象的继承"。
Three.js 的真正哲学是"拼装":Scene 组装 Mesh、Mesh 组装 Geometry、Material、光照、动画......
📦 6. 模块化导出你的宇宙组件
javascript
// index.js
export * from './geometry';
export * from './materials';
export * from './scene';
export * from './controls';
export * from './utils';
你甚至可以做成 NPM 包:
csharp
npm init
pnpm add three
并导出:
json
// package.json
{
"name": "threekit",
"exports": {
"./box": "./geometry/createBox.js"
}
}
🛸 延伸玩法:让组件懂动画、懂交互、懂感情
你可以封装更多高级工具:
- 🔁
addSpinAnimation(object3D)
- 🎯
raycastOnClick(object3D, camera, domElement)
- 💓
pulseMaterial(material)
这些是你库的灵魂魔法,让它不仅只是"组件",而是一个"3D 引擎的小脑袋瓜"。
🧠 总结:组件化的三维宇宙观
模块 | 意义 |
---|---|
Geometry 封装 | 基础积木形状 |
Material 封装 | 风格与美学表达 |
控制器 | 用户与世界的接口 |
Scene | 舞台与背景 |
交互逻辑 | 精神内核 |
封装的目的不是简化,而是赋予抽象新的语义维度。你是宇宙的构建者,不只是调用者。
💬 彩蛋:哲学时间
"我们终究要构建属于自己的三维组件库。
就像小时候拼乐高,我们不只是为了搭一个飞机,
而是为了知道:原来世界可以用块状的美感组合。"
🔗 下一步建议
- 使用 TypeScript 类型定义你的参数,避免黑盒
- 结合 Vue/React 等框架封装渲染生命周期
- 集成性能工具如
stats.js
、lil-gui
- 写文档 + 在线示例,构建开发者社区