
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js CDN入门</title>
<style>
body { margin: 0; overflow: hidden; }
#info {
position: absolute;
top: 10px;
width: 100%;
text-align: center;
color: white;
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<!-- Three.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/build/three.min.js"></script>
<!-- 常用控制器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/controls/OrbitControls.js"></script>
<!-- 性能监控 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/libs/stats.min.js"></script>
<!-- GUI调试 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/libs/dat.gui.min.js"></script>
<div id="info">Three.js CDN示例 - FPS: <span id="fps">0</span></div>
<script>
// 基础Three.js代码
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 添加立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
// 性能监控
const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild(stats.dom);
// 动画循环
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
// 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
bash
# 1. 创建项目
npm create vite@latest threejs-project -- --template vanilla
cd threejs-project
# 2. 安装Three.js及相关依赖
npm install three
npm install -D @types/three vite-plugin-three
# 3. 安装常用扩展
npm install @react-three/fiber @react-three/drei # React集成
npm install three-stdlib # 官方示例工具
npm install cannon-es # 物理引擎
npm install gsap # 动画库
vite.config.js 配置:
javascript
import { defineConfig } from 'vite';
import three from 'vite-plugin-three';
export default defineConfig({
plugins: [three()],
build: {
target: 'esnext', // 支持现代ES特性
sourcemap: true, // 便于调试
rollupOptions: {
output: {
manualChunks: {
'three': ['three'], // 单独打包Three.js
'vendor': ['cannon-es', 'gsap'] // 第三方库
}
}
}
},
server: {
host: true, // 允许外部访问
port: 3000,
open: true // 自动打开浏览器
}
});
package.json 脚本配置:
json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"analyze": "vite-bundle-analyzer",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,json,css,md}"
},
"dependencies": {
"three": "^0.135.0",
"three-stdlib": "^2.8.5",
"cannon-es": "^0.19.0",
"gsap": "^3.10.4"
},
"devDependencies": {
"@types/three": "^0.135.0",
"vite": "^3.0.0",
"vite-plugin-three": "^0.2.2",
"@vitejs/plugin-legacy": "^2.0.0",
"eslint": "^8.0.0",
"prettier": "^2.0.0",
"vite-bundle-analyzer": "^0.6.0"
}
}
主入口文件 main.js:
javascript
import * as THREE from 'three';
// 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
// 相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 2, 5);
// 渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["three", "vite/client"],
"allowSyntheticDefaultImports": true
},
"include": ["src"]
}
bash
# 创建React + Three.js项目
npx create-react-app threejs-react-app --template typescript
cd threejs-react-app
npm install @react-three/fiber @react-three/drei drei-stats
npm install three @types/three
App.tsx:
tsx
import React, { useRef, useState } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls, Stats, Environment, Box } from '@react-three/drei';
import * as THREE from 'three';
function RotatingBox() {
const meshRef = useRef<THREE.Mesh>(null);
useFrame((state) => {
if (meshRef.current) {
meshRef.current.rotation.x = state.clock.elapsedTime;
meshRef.current.rotation.y = state.clock.elapsedTime * 0.5;
}
});
return (
<Box ref={meshRef} args={[1, 1, 1]}>
<meshStandardMaterial color="hotpink" />
</Box>
);
}
function Scene() {
const { camera } = useThree();
return (
<>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 10, 7]} intensity={1} castShadow />
<RotatingBox />
<OrbitControls camera={camera} />
<Environment preset="city" />
<Stats />
</>
);
}
function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<Canvas
shadows
camera={{ position: [0, 2, 5], fov: 75 }}
gl={{
antialias: true,
alpha: true,
powerPreference: 'high-performance'
}}
>
<Scene />
</Canvas>
</div>
);
}
export default App;
推荐配置
.vscode/settings.json:
json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.associations": {
"*.glsl": "glsl",
"*.vert": "glsl",
"*.frag": "glsl",
"*.vs": "glsl",
"*.fs": "glsl"
},
"emmet.includeLanguages": {
"javascript": "javascriptreact"
},
"typescript.preferences.importModuleSpecifier": "relative",
"javascript.preferences.importModuleSpecifier": "relative",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
.vscode/extensions.json:
json
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-next",
"slevesque.shader",
"ritwickdey.liveserver",
"threejs.threejs-snippets",
"ms-vscode.live-server",
"zixuanchen.vitest-explorer"
]
}
text
优势:
- 强大的Three.js代码补全
- 内置GLSL支持
- 性能分析工具集成
- 调试WebGL上下文
配置建议:
1. 启用Node.js Core library
2. 配置GLSL文件关联
3. 安装Three.js插件
4. 配置WebGL调试器
text
在线地址:https://threejs.org/editor/
功能特点:
✓ 可视化场景编辑
✓ 实时材质调整
✓ 模型导入导出
✓ 代码生成导出
使用场景:
- 快速原型设计
- 材质和光照测试
- 场景布局预览
javascript
class AssetManager {
constructor() {
this.textures = new Map();
this.models = new Map();
this.audio = new Map();
this.fonts = new Map();
this.loadingManager = new THREE.LoadingManager();
this.setupLoadingManager();
this.initLoaders();
}
setupLoadingManager() {
this.loadingManager.onStart = (url, itemsLoaded, itemsTotal) => {
console.log(`开始加载: ${url}, 进度: ${itemsLoaded}/${itemsTotal}`);
this.showLoadingScreen();
};
this.loadingManager.onLoad = () => {
console.log('所有资源加载完成');
this.hideLoadingScreen();
};
this.loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
const progress = (itemsLoaded / itemsTotal) * 100;
console.log(`加载中: ${url}, 进度: ${progress.toFixed(2)}%`);
this.updateProgressBar(progress);
};
this.loadingManager.onError = (url) => {
console.error(`加载失败: ${url}`);
};
}
initLoaders() {
this.textureLoader = new THREE.TextureLoader(this.loadingManager);
this.gltfLoader = new GLTFLoader(this.loadingManager);
this.fontLoader = new FontLoader(this.loadingManager);
this.audioLoader = new THREE.AudioLoader(this.loadingManager);
// 配置DRACO压缩
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.5/');
dracoLoader.setDecoderConfig({ type: 'js' });
this.gltfLoader.setDRACOLoader(dracoLoader);
}
async loadTexture(key, url) {
return new Promise((resolve, reject) => {
if (this.textures.has(key)) {
resolve(this.textures.get(key));
return;
}
this.textureLoader.load(
url,
(texture) => {
texture.encoding = THREE.sRGBEncoding;
this.textures.set(key, texture);
resolve(texture);
},
undefined,
(error) => reject(error)
);
});
}
async loadModel(key, url) {
return new Promise((resolve, reject) => {
if (this.models.has(key)) {
resolve(this.models.get(key));
return;
}
this.gltfLoader.load(
url,
(gltf) => {
this.models.set(key, gltf);
resolve(gltf);
},
undefined,
(error) => reject(error)
);
});
}
async loadFont(key, url) {
return new Promise((resolve, reject) => {
if (this.fonts.has(key)) {
resolve(this.fonts.get(key));
return;
}
this.fontLoader.load(
url,
(font) => {
this.fonts.set(key, font);
resolve(font);
},
undefined,
(error) => reject(error)
);
});
}
getTexture(key) {
return this.textures.get(key);
}
getModel(key) {
return this.models.get(key);
}
dispose() {
// 释放所有资源
this.textures.forEach(texture => texture.dispose());
this.models.forEach(model => {
model.scene.traverse((obj) => {
if (obj.isMesh) {
obj.geometry.dispose();
if (Array.isArray(obj.material)) {
obj.material.forEach(material => material.dispose());
} else {
obj.material.dispose();
}
}
});
});
this.textures.clear();
this.models.clear();
this.audio.clear();
this.fonts.clear();
}
showLoadingScreen() {
if (!this.loadingScreen) {
this.loadingScreen = document.createElement('div');
this.loadingScreen.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #1a1a2e;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999;
color: white;
font-family: Arial, sans-serif;
`;
this.progressBar = document.createElement('div');
this.progressBar.style.cssText = `
width: 300px;
height: 20px;
background: #333;
border-radius: 10px;
margin-top: 20px;
overflow: hidden;
`;
this.progressFill = document.createElement('div');
this.progressFill.style.cssText = `
width: 0%;
height: 100%;
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
transition: width 0.3s ease;
`;
this.progressBar.appendChild(this.progressFill);
this.loadingScreen.innerHTML = '<h2>加载资源中...</h2>';
this.loadingScreen.appendChild(this.progressBar);
document.body.appendChild(this.loadingScreen);
}
}
updateProgressBar(progress) {
if (this.progressFill) {
this.progressFill.style.width = `${progress}%`;
}
}
hideLoadingScreen() {
if (this.loadingScreen) {
setTimeout(() => {
this.loadingScreen.style.opacity = '0';
this.loadingScreen.style.transition = 'opacity 0.5s ease';
setTimeout(() => {
if (this.loadingScreen.parentNode) {
this.loadingScreen.parentNode.removeChild(this.loadingScreen);
}
this.loadingScreen = null;
}, 500);
}, 500);
}
}
}
// 使用示例
const assetManager = new AssetManager();
await assetManager.loadModel('character', '/models/character.glb');
await assetManager.loadTexture('metal', '/textures/metal.jpg');
webpack.config.js(备选方案):
javascript
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[contenthash].js',
clean: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(glsl|vs|fs|vert|frag)$/,
exclude: /node_modules/,
use: ['raw-loader', 'glslify-loader']
},
{
test: /\.(png|jpg|jpeg|gif|hdr|exr)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[hash][ext][query]'
}
}
]
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除console
drop_debugger: true // 移除debugger
},
output: {
comments: false
}
}
})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
three: {
test: /[\\/]node_modules[\\/](three)[\\/]/,
name: 'three',
chunks: 'all',
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false
}),
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
],
performance: {
hints: 'warning',
maxAssetSize: 1024 * 1024, // 1MB
maxEntrypointSize: 1024 * 1024 * 2, // 2MB
assetFilter: function(assetFilename) {
return !assetFilename.endsWith('.map');
}
}
};

bash
# 使用官方模板快速开始
npx degit mrdoob/three.js/examples/webgl_animation_cloth my-threejs-project
cd my-threejs-project
# 或使用社区模板
# React: https://github.com/pmndrs/react-three-fiber
# Vue: https://github.com/troisjs/trois
# Svelte: https://github.com/hugozap/svelte-threejs
javascript
// 1. WebGL上下文创建失败
try {
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: 'high-performance'
});
} catch (error) {
console.error('WebGL不支持,尝试降级:', error);
// 降级方案
const renderer = new THREE.WebGLRenderer({
powerPreference: 'low-power'
});
}
// 2. 内存泄漏检测
function checkMemoryLeaks() {
const startMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
// 执行一些操作后...
const endMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
if (endMemory - startMemory > 10 * 1024 * 1024) { // 10MB
console.warn('可能的内存泄漏');
}
}
// 3. 跨域资源加载
const textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = 'anonymous';
// 4. 移动端适配问题
function checkMobileSupport() {
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) {
// 降低渲染质量
renderer.setPixelRatio(1);
// 减少多边形数量
// 禁用阴影等消耗性能的特性
}
}
通过合理的环境配置和工具选择,可以极大提升Three.js开发效率和项目质量。建议:
初学者:从CDN开始,快速体验
正式项目:使用Vite + NPM + TypeScript
React项目:选择React Three Fiber生态
性能关键:配置完整的监控和优化工具
团队协作:统一开发环境和代码规范
开发工具:VSCode免费快捷,配置灵活【推荐】
开发工具:WebStorm企业级选择,功能强大