Three.js 开发环境配置
良好的开发环境能显著提高开发效率和代码质量。本章节将介绍如何搭建完整的 Three.js 开发环境。
开发工具
代码编辑器
Visual Studio Code(推荐)
VS Code 是最流行的前端开发编辑器,对 Three.js 有很好的支持。
推荐插件:
-
Three.js Autocomplete
- 提供 Three.js API 自动补全
- 安装:搜索 "three.js autocomplete"
-
Shader Language Support
- GLSL 着色器语法高亮和智能提示
- 安装:搜索 "Shader Language Support"
-
Live Server
- 本地开发服务器,支持热重载
- 安装:搜索 "Live Server"
-
Prettier
- 代码格式化工具
- 安装:搜索 "Prettier - Code formatter"
-
ESLint
- JavaScript 代码检查
- 安装:搜索 "ESLint"
VS Code 配置:
在项目根目录创建 .vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.associations": {
"*.glsl": "glsl",
"*.vert": "glsl",
"*.frag": "glsl"
}
}WebStorm
JetBrains 出品的专业前端 IDE,功能强大但收费。
优势:
- 内置强大的代码智能提示
- 集成调试工具
- Git 集成
- 数据库工具
浏览器开发工具
Chrome DevTools
Chrome 浏览器的开发者工具是调试 Three.js 的利器。
常用功能:
-
控制台(Console)
javascript// 查看 Three.js 对象 console.log(scene) console.log(camera) console.log(renderer.info) // 查看渲染信息 -
元素面板(Elements)
- 查看 canvas 元素
- 检查 DOM 结构
-
性能面板(Performance)
- 录制性能数据
- 分析渲染瓶颈
-
内存面板(Memory)
- 检测内存泄漏
- 查看对象内存占用
Three.js DevTools 扩展:
安装 Chrome 扩展:Three.js DevTools
功能:
- 查看场景树结构
- 检查对象属性
- 实时编辑对象
- 性能监控
项目构建工具
Vite(推荐)
Vite 是新一代前端构建工具,启动速度快,配置简单。
安装 Vite
npm create vite@latest threejs-project -- --template vanilla
cd threejs-project
npm install three项目结构
threejs-project/
├── index.html
├── src/
│ ├── main.js
│ ├── style.css
│ └── scenes/
│ └── basic.js
├── public/
│ └── models/
├── package.json
└── vite.config.js配置文件
vite.config.js:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 3000,
open: true, // 自动打开浏览器
host: true // 允许局域网访问
},
build: {
outDir: 'dist',
assetsDir: 'assets'
},
optimizeDeps: {
include: ['three']
}
})npm scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}Webpack
传统但功能强大的构建工具。
安装
npm install -D webpack webpack-cli webpack-dev-server
npm install -D babel-loader @babel/core @babel/preset-env
npm install -D html-webpack-plugin
npm install three配置文件
webpack.config.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.(glsl|vert|frag)$/,
type: 'asset/source'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
})
],
devServer: {
static: './dist',
hot: true,
port: 3000
},
resolve: {
extensions: ['.js']
}
}Parcel
零配置构建工具,适合快速原型开发。
安装
npm install -D parcel
npm install three使用
# 开发
npx parcel index.html
# 构建
npx parcel build index.htmlTypeScript 支持
Three.js 自带 TypeScript 类型定义,推荐使用 TypeScript 开发。
配置 TypeScript
安装
npm install -D typescript
npm install threetsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2020", "DOM"],
"types": ["vite/client"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}TypeScript 示例
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
class ThreeScene {
private scene: THREE.Scene
private camera: THREE.PerspectiveCamera
private renderer: THREE.WebGLRenderer
private controls: OrbitControls
private cube: THREE.Mesh
constructor() {
this.scene = new THREE.Scene()
this.camera = this.createCamera()
this.renderer = this.createRenderer()
this.cube = this.createCube()
this.controls = this.createControls()
this.addLights()
this.setupEventListeners()
this.animate()
}
private createCamera(): THREE.PerspectiveCamera {
const aspect = window.innerWidth / window.innerHeight
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000)
camera.position.set(0, 0, 5)
return camera
}
private createRenderer(): THREE.WebGLRenderer {
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
document.body.appendChild(renderer.domElement)
return renderer
}
private createCube(): THREE.Mesh {
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({
color: 0x00ff00,
metalness: 0.5,
roughness: 0.5
})
const mesh = new THREE.Mesh(geometry, material)
this.scene.add(mesh)
return mesh
}
private createControls(): OrbitControls {
const controls = new OrbitControls(this.camera, this.renderer.domElement)
controls.enableDamping = true
return controls
}
private addLights(): void {
const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
this.scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.set(5, 5, 5)
this.scene.add(directionalLight)
}
private setupEventListeners(): void {
window.addEventListener('resize', this.onWindowResize.bind(this))
}
private onWindowResize(): void {
this.camera.aspect = window.innerWidth / window.innerHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(window.innerWidth, window.innerHeight)
}
private animate(): void {
requestAnimationFrame(this.animate.bind(this))
this.cube.rotation.x += 0.01
this.cube.rotation.y += 0.01
this.controls.update()
this.renderer.render(this.scene, this.camera)
}
}
// 创建场景实例
new ThreeScene()调试工具
Stats.js
实时显示帧率(FPS)和渲染时间。
安装
npm install stats.js使用
import Stats from 'stats.js'
const stats = new Stats()
stats.showPanel(0) // 0: fps, 1: ms, 2: mb
document.body.appendChild(stats.dom)
function animate() {
stats.begin()
// 渲染代码
stats.end()
requestAnimationFrame(animate)
}
animate()面板说明:
| 面板 | 快捷键 | 显示内容 |
|---|---|---|
| FPS | 0 | 每秒帧数 |
| MS | 1 | 每帧渲染时间(毫秒) |
| MB | 2 | 内存占用(需浏览器支持) |
多面板显示:
// 同时显示多个面板
const statsFPS = new Stats()
statsFPS.showPanel(0)
document.body.appendChild(statsFPS.dom)
const statsMS = new Stats()
statsMS.showPanel(1)
statsMS.dom.style.left = '100px'
document.body.appendChild(statsMS.dom)Three.js Inspector
浏览器扩展,用于检查 Three.js 场景。
安装
- Chrome: Three.js Inspector
- Firefox: Three.js Inspector
使用
- 安装扩展
- 打开包含 Three.js 的网页
- 按 F12 打开开发者工具
- 切换到 "Three.js" 标签页
主要功能:
- 查看场景树结构
- 检查对象属性(位置、旋转、材质等)
- 实时编辑对象属性
- 查看几何体和材质信息
- 性能分析
dat.GUI / lil-gui
创建可视化调试面板,实时调整参数。lil-gui 是 dat.gui 的现代替代品。
安装
# dat.gui(传统)
npm install dat.gui
# lil-gui(推荐,更现代)
npm install lil-guilil-gui 使用
import GUI from 'lil-gui'
const gui = new GUI({ title: '调试面板' })
// 参数对象
const params = {
rotationSpeed: 0.01,
color: '#00ff00',
wireframe: false,
scale: 1,
metalness: 0.5,
roughness: 0.5
}
// 添加控制项
gui.add(params, 'rotationSpeed', 0, 0.1).name('旋转速度')
gui.addColor(params, 'color').name('颜色').onChange((value) => {
cube.material.color.set(value)
})
gui.add(params, 'wireframe').name('线框模式').onChange((value) => {
cube.material.wireframe = value
})
gui.add(params, 'scale', 0.1, 2).name('缩放').onChange((value) => {
cube.scale.set(value, value, value)
})
// 文件夹组织
const materialFolder = gui.addFolder('材质')
materialFolder.add(params, 'metalness', 0, 1).name('金属度')
materialFolder.add(params, 'roughness', 0, 1).name('粗糙度')
materialFolder.open() // 默认展开
// 保存配置
gui.save()lil-gui 控制类型:
| 方法 | 说明 | 示例 |
|---|---|---|
add(obj, prop) | 数字/布尔值 | gui.add(params, 'speed', 0, 10) |
addColor(obj, prop) | 颜色选择器 | gui.addColor(params, 'color') |
addFolder(name) | 创建文件夹 | gui.addFolder('材质') |
addButton(obj, prop) | 按钮 | gui.add(params, 'reset') |
Chrome DevTools 调试技巧
1. 控制台调试
// 查看 Three.js 对象
console.log(scene)
console.log(camera)
console.log(renderer.info) // 渲染统计
// 查看场景中所有对象
scene.traverse((obj) => {
console.log(obj.type, obj.uuid, obj.name)
})
// 查看对象的几何体信息
console.log(mesh.geometry.attributes.position.count) // 顶点数
console.log(mesh.geometry.index.count / 3) // 三角形数
// 检测对象是否在视锥体内
const frustum = new THREE.Frustum()
frustum.setFromProjectionMatrix(
new THREE.Matrix4().multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
)
)
console.log(frustum.intersectsObject(mesh))2. 使用 console.table
// 以表格形式显示对象属性
console.table({
vertices: mesh.geometry.attributes.position.count,
triangles: mesh.geometry.index.count / 3,
drawCalls: renderer.info.render.calls
})3. 性能分析
// 使用 console.time 测量执行时间
console.time('render')
renderer.render(scene, camera)
console.timeEnd('render')
// 使用 Performance API
performance.mark('renderStart')
renderer.render(scene, camera)
performance.mark('renderEnd')
performance.measure('render', 'renderStart', 'renderEnd')
console.log(performance.getEntriesByName('render'))4. 断点调试
// 条件断点
function animate() {
if (cube.position.y > 2) {
debugger // 当 y > 2 时触发断点
}
cube.rotation.y += 0.01
renderer.render(scene, camera)
requestAnimationFrame(animate)
}5. 内存分析
// 检测内存泄漏
// 1. 打开 Chrome DevTools -> Memory
// 2. 选择 "Heap snapshot"
// 3. 执行操作前后各拍摄一次快照
// 4. 比较两次快照的差异
// 手动触发垃圾回收(需 Chrome 启动参数 --expose-gc)
if (typeof gc === 'function') {
gc()
console.log('垃圾回收完成')
}
// 检查对象引用
console.log(mesh.geometry) // 应该有引用
mesh.geometry.dispose()
mesh.geometry = null // 释放引用调试辅助函数
// 创建调试辅助对象
class DebugHelpers {
constructor(scene) {
this.scene = scene
}
// 添加坐标轴
addAxesHelper(size = 5) {
const axesHelper = new THREE.AxesHelper(size)
this.scene.add(axesHelper)
return axesHelper
}
// 添加网格
addGridHelper(size = 10, divisions = 10) {
const gridHelper = new THREE.GridHelper(size, divisions)
this.scene.add(gridHelper)
return gridHelper
}
// 显示对象包围盒
showBoundingBox(mesh) {
const box = new THREE.Box3().setFromObject(mesh)
const helper = new THREE.Box3Helper(box, 0xff0000)
this.scene.add(helper)
return helper
}
// 显示相机视锥体
showCameraFrustum(camera) {
const helper = new THREE.CameraHelper(camera)
this.scene.add(helper)
return helper
}
// 显示光源
showLightHelper(light) {
let helper
if (light instanceof THREE.DirectionalLight) {
helper = new THREE.DirectionalLightHelper(light, 1)
} else if (light instanceof THREE.PointLight) {
helper = new THREE.PointLightHelper(light, 0.5)
} else if (light instanceof THREE.SpotLight) {
helper = new THREE.SpotLightHelper(light)
}
if (helper) this.scene.add(helper)
return helper
}
// 显示法线
showNormals(mesh, size = 0.1) {
const helper = new THREE.VertexNormalsHelper(mesh, size)
this.scene.add(helper)
return helper
}
}
// 使用
const debug = new DebugHelpers(scene)
debug.addAxesHelper()
debug.addGridHelper()
debug.showCameraFrustum(camera)性能监控
Renderer Info
Three.js 提供了渲染器信息 API:
console.log(renderer.info)
// {
// memory: { geometries: 1, textures: 1 },
// render: { calls: 2, triangles: 36, points: 0, lines: 0 }
// }自定义性能监控
class PerformanceMonitor {
constructor() {
this.fps = 0
this.frames = 0
this.lastTime = performance.now()
}
update() {
this.frames++
const currentTime = performance.now()
if (currentTime >= this.lastTime + 1000) {
this.fps = Math.round((this.frames * 1000) / (currentTime - this.lastTime))
this.frames = 0
this.lastTime = currentTime
console.log(`FPS: ${this.fps}`)
console.log(`Draw Calls: ${renderer.info.render.calls}`)
console.log(`Triangles: ${renderer.info.render.triangles}`)
}
}
}
// 使用
const monitor = new PerformanceMonitor()
function animate() {
monitor.update()
// 渲染代码
requestAnimationFrame(animate)
}热重载配置
Vite HMR
Vite 自带热重载功能,修改代码后自动刷新。
// vite.config.js
export default {
server: {
hot: true
}
}Webpack HMR
// webpack.config.js
module.exports = {
devServer: {
hot: true,
liveReload: true
}
}ESLint 配置
安装
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
// 自定义规则
'no-console': 'warn',
'no-unused-vars': 'error'
}
}Prettier 配置
安装
npm install -D prettier.prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100
}项目模板
创建一个可复用的项目模板:
模板结构
threejs-template/
├── .vscode/
│ └── settings.json
├── public/
│ └── models/
├── src/
│ ├── main.ts
│ ├── scenes/
│ │ └── BaseScene.ts
│ ├── utils/
│ │ └── helpers.ts
│ └── styles/
│ └── main.css
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── .eslintrc.js
└── .prettierrcBaseScene.ts
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Stats from 'stats.js'
export abstract class BaseScene {
protected scene: THREE.Scene
protected camera: THREE.PerspectiveCamera
protected renderer: THREE.WebGLRenderer
protected controls: OrbitControls
protected stats: Stats
protected clock: THREE.Clock
constructor() {
this.scene = this.createScene()
this.camera = this.createCamera()
this.renderer = this.createRenderer()
this.controls = this.createControls()
this.stats = this.createStats()
this.clock = new THREE.Clock()
this.addLights()
this.setupEventListeners()
this.init()
this.animate()
}
protected createScene(): THREE.Scene {
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x333333)
return scene
}
protected createCamera(): THREE.PerspectiveCamera {
const aspect = window.innerWidth / window.innerHeight
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000)
camera.position.set(0, 0, 5)
return camera
}
protected createRenderer(): THREE.WebGLRenderer {
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.shadowMap.enabled = true
document.body.appendChild(renderer.domElement)
return renderer
}
protected createControls(): OrbitControls {
const controls = new OrbitControls(this.camera, this.renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.05
return controls
}
protected createStats(): Stats {
const stats = new Stats()
stats.showPanel(0)
document.body.appendChild(stats.dom)
return stats
}
protected addLights(): void {
const ambientLight = new THREE.AmbientLight(0x404040, 0.5)
this.scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
directionalLight.position.set(5, 5, 5)
directionalLight.castShadow = true
this.scene.add(directionalLight)
}
protected setupEventListeners(): void {
window.addEventListener('resize', this.onWindowResize.bind(this))
}
protected onWindowResize(): void {
this.camera.aspect = window.innerWidth / window.innerHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(window.innerWidth, window.innerHeight)
}
protected abstract init(): void
protected abstract update(deltaTime: number): void
protected animate(): void {
requestAnimationFrame(this.animate.bind(this))
this.stats.begin()
const deltaTime = this.clock.getDelta()
this.update(deltaTime)
this.controls.update()
this.renderer.render(this.scene, this.camera)
this.stats.end()
}
}使用模板
import { BaseScene } from './scenes/BaseScene'
import * as THREE from 'three'
class MyScene extends BaseScene {
private cube: THREE.Mesh
protected init(): void {
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
this.cube = new THREE.Mesh(geometry, material)
this.scene.add(this.cube)
}
protected update(deltaTime: number): void {
this.cube.rotation.x += 0.01
this.cube.rotation.y += 0.01
}
}
new MyScene()最佳实践
1. 使用版本控制
git init
git add .
git commit -m "Initial commit"添加 .gitignore:
node_modules/
dist/
.DS_Store
*.log
.env2. 使用环境变量
# .env.development
VITE_APP_TITLE=Three.js Development
VITE_API_URL=http://localhost:3000// 使用环境变量
console.log(import.meta.env.VITE_APP_TITLE)3. 代码组织
src/
├── components/ # 可复用组件
├── scenes/ # 场景类
├── utils/ # 工具函数
├── assets/ # 静态资源
├── loaders/ # 加载器
├── materials/ # 自定义材质
└── shaders/ # 着色器代码4. 添加文档
npm install -D typedoc{
"scripts": {
"doc": "typedoc --out docs src"
}
}常见问题解答
Q1: 如何解决 WebGL 上下文丢失问题?
A: WebGL 上下文丢失可能发生在系统资源不足、浏览器切换标签页等情况下。
// 监听上下文丢失事件
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault()
console.log('WebGL 上下文丢失')
// 停止渲染循环
// 显示提示信息给用户
})
// 监听上下文恢复事件
renderer.domElement.addEventListener('webglcontextrestored', () => {
console.log('WebGL 上下文已恢复')
// 重新初始化资源
initScene()
// 恢复渲染循环
animate()
})
// 强制丢失上下文(测试用)
// renderer.forceContextLoss()Q2: 如何正确处理资源释放避免内存泄漏?
A: Three.js 对象需要手动释放,特别是在单页应用中切换场景时。
// 释放场景资源的完整函数
function disposeScene(scene, renderer) {
// 遍历场景中所有对象
scene.traverse((object) => {
// 释放几何体
if (object.geometry) {
object.geometry.dispose()
}
// 释放材质(可能有多个材质)
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach((material) => disposeMaterial(material))
} else {
disposeMaterial(object.material)
}
}
})
// 清空场景
scene.clear()
// 释放渲染器
renderer.dispose()
}
// 释放材质及其贴图
function disposeMaterial(material) {
// 释放所有贴图属性
const textureProperties = [
'map', 'normalMap', 'roughnessMap', 'metalnessMap',
'aoMap', 'emissiveMap', 'alphaMap', 'envMap'
]
textureProperties.forEach((prop) => {
if (material[prop] && typeof material[prop].dispose === 'function') {
material[prop].dispose()
}
})
material.dispose()
}Q3: 如何在 TypeScript 中正确配置 Three.js 类型?
A: Three.js 自带类型定义,但有时需要额外配置。
// tsconfig.json
{
"compilerOptions": {
"types": ["vite/client"],
"typeRoots": ["./node_modules/@types", "./src/types"]
}
}
// 扩展 Three.js 类型(如果需要)
// src/types/three.d.ts
import 'three'
declare module 'three' {
interface Object3D {
customProperty?: string
}
}
// 处理导入的类型问题
import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 或者在 vite.config.ts 中配置
import { defineConfig } from 'vite'
export default defineConfig({
optimizeDeps: {
include: ['three', 'three/examples/jsm/controls/OrbitControls']
}
})Q4: 如何配置 VS Code 实现更好的 Three.js 开发体验?
A: 创建完整的 VS Code 配置。
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"files.associations": {
"*.glsl": "glsl",
"*.vert": "glsl",
"*.frag": "glsl",
"*.vs": "glsl",
"*.fs": "glsl"
},
"typescript.preferences.importModuleSpecifier": "relative",
"javascript.preferences.importModuleSpecifier": "relative"
}
// .vscode/launch.json(调试配置)
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMaps": true
}
]
}
// .vscode/extensions.json(推荐扩展)
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
]
}Q5: 如何解决 Vite 开发服务器加载模型报错?
A: Vite 需要正确配置静态资源处理。
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
// 1. 配置 public 目录
publicDir: 'public',
// 2. 配置资源处理
assetsInclude: ['**/*.gltf', '**/*.glb', '**/*.obj', '**/*.fbx'],
server: {
// 3. 配置 CORS
cors: true,
// 4. 配置代理(如果模型在其他服务器)
proxy: {
'/models': {
target: 'http://example.com',
changeOrigin: true
}
}
},
build: {
// 5. 配置资源内联阈值
assetsInlineLimit: 0 // 禁用资源内联
}
})
// 正确加载模型
// 将模型放在 public/models/ 目录下
const loader = new GLTFLoader()
loader.load('/models/scene.glb', (gltf) => {
scene.add(gltf.scene)
})
// 或使用 import 导入(需要配置)
import modelUrl from '../assets/model.glb?url'
loader.load(modelUrl, (gltf) => {
scene.add(gltf.scene)
})Q6: 如何实现热重载时保持 Three.js 场景状态?
A: 使用 Vite HMR API 保持状态。
// main.js
import * as THREE from 'three'
// 将状态存储在全局对象中
if (!window.__THREE_STATE__) {
window.__THREE_STATE__ = {
cameraPosition: null,
objects: []
}
}
class SceneManager {
constructor() {
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera()
this.renderer = new THREE.WebGLRenderer()
// 恢复状态
this.restoreState()
// 监听 HMR
if (import.meta.hot) {
// 保存当前状态
import.meta.hot.dispose(() => {
window.__THREE_STATE__.cameraPosition = this.camera.position.clone()
})
// 接受更新
import.meta.hot.accept(() => {
console.log('模块更新完成')
})
}
}
restoreState() {
if (window.__THREE_STATE__.cameraPosition) {
this.camera.position.copy(window.__THREE_STATE__.cameraPosition)
}
}
}
new SceneManager()Q7: 如何配置 ESLint 支持 Three.js 开发?
A: 创建针对 Three.js 的 ESLint 配置。
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
// Three.js 常用规则调整
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
// 允许全局 THREE 变量(CDN 方式)
'no-undef': 'off',
// 关闭 any 类型警告(Three.js 类型定义不完善时)
'@typescript-eslint/no-explicit-any': 'off',
// 自定义规则
'no-console': ['warn', { allow: ['warn', 'error'] }]
},
globals: {
THREE: 'readonly' // CDN 方式使用
}
}Q8: 如何在开发环境快速切换调试模式?
A: 创建开发环境配置工具。
// src/utils/DevTools.ts
export class DevTools {
private gui: GUI | null = null
private stats: Stats | null = null
private helpers: THREE.Object3D[] = []
constructor(private scene: THREE.Scene, private enabled: boolean = true) {
if (!enabled) return
this.init()
}
private init() {
// 创建 GUI
this.gui = new GUI({ title: '开发工具' })
// 创建 Stats
this.stats = new Stats()
this.stats.showPanel(0)
document.body.appendChild(this.stats.dom)
// 添加辅助对象
this.helpers.push(new THREE.AxesHelper(5))
this.helpers.push(new THREE.GridHelper(10, 10))
this.helpers.forEach(h => this.scene.add(h))
// 添加全局控制
const globalFolder = this.gui.addFolder('全局')
const globalParams = {
showHelpers: true,
showStats: true
}
globalFolder.add(globalParams, 'showHelpers').onChange((v) => {
this.helpers.forEach(h => h.visible = v)
})
globalFolder.add(globalParams, 'showStats').onChange((v) => {
this.stats.dom.style.display = v ? 'block' : 'none'
})
}
begin() {
this.stats?.begin()
}
end() {
this.stats?.end()
}
dispose() {
this.gui?.destroy()
this.stats?.dom.remove()
this.helpers.forEach(h => {
this.scene.remove(h)
h.dispose?.()
})
}
}
// 使用
const devTools = new DevTools(scene, import.meta.env.DEV)
function animate() {
devTools.begin()
// ... 渲染代码
devTools.end()
requestAnimationFrame(animate)
}下一步
现在你已经配置好了完整的开发环境,接下来可以: