1. 魔方与茶文化的技术融合:从物理模拟到交互体验
在技术开发领域,将传统益智玩具与现代交互设计相结合,往往能创造出令人耳目一新的用户体验。魔方作为经典的立体拼图玩具,其机械结构和旋转逻辑本身就蕴含着丰富的算法思想;而茶道文化中的"拧"、"泡"等动作,则为我们提供了自然直观的交互隐喻。本文将带领大家实现一个融合魔方旋转与茶艺动作的交互式应用,涵盖3D建模、物理引擎、手势识别等关键技术点。
这个项目特别适合有一定前端基础、希望进阶学习WebGL和交互设计的开发者。通过学习,你将掌握Three.js的3D渲染技术、Cannon.js物理引擎的集成方法,以及如何将传统文化元素转化为数字交互体验。无论是用于教育展示、娱乐应用还是文化传播,这套技术方案都具有很高的实用价值。
2. 技术选型与环境准备
2.1 核心技术栈说明
本项目采用现代前端技术栈,主要基于以下框架和库:
- Three.js:负责3D场景的渲染和动画控制,提供强大的WebGL封装
- Cannon.js:物理引擎,模拟魔方的重力、碰撞和旋转物理效果
- Hammer.js:手势识别库,处理触摸屏上的旋转、拧动等操作
- GSAP:动画库,用于创建流畅的过渡效果和交互反馈
2.2 开发环境配置
首先确保你的开发环境满足以下要求:
# 创建项目目录结构 mkdir magic-cube-tea-app cd magic-cube-tea-app # 初始化npm项目 npm init -y # 安装核心依赖 npm install three cannon-es hammerjs gsap npm install --save-dev webpack webpack-cli webpack-dev-server项目基础目录结构如下:
src/ ├── js/ │ ├── core/ # 核心模块 │ │ ├── SceneManager.js │ │ ├── CubeManager.js │ │ └── PhysicsManager.js │ ├── utils/ # 工具函数 │ │ ├── GestureHandler.js │ │ └── AnimationHelper.js │ └── main.js # 入口文件 ├── css/ │ └── style.css # 样式文件 ├── assets/ # 资源文件 │ ├── textures/ # 纹理贴图 │ └── models/ # 3D模型 └── index.html # 主页面2.3 基础HTML结构搭建
创建主页面文件,设置基本的3D渲染容器:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>魔方茶艺交互体验</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <div id="container"> <div id="canvas-container"></div> <div id="ui-controls"> <button id="shake-btn">摇一摇魔方</button> <button id="twist-btn">拧一拧茶壶</button> <div id="gesture-hint">手势操作提示区域</div> </div> </div> <script src="js/main.js"></script> </body> </html>3. 3D场景搭建与魔方建模
3.1 Three.js场景初始化
创建场景管理器,负责3D环境的初始化和渲染循环:
// js/core/SceneManager.js import * as THREE from 'three'; export class SceneManager { constructor(containerId) { this.container = document.getElementById(containerId); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.init(); } init() { // 设置渲染器参数 this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setClearColor(0xf0f0f0); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 将画布添加到容器 this.container.appendChild(this.renderer.domElement); // 设置相机位置 this.camera.position.set(0, 5, 10); this.camera.lookAt(0, 0, 0); // 添加基础灯光 this.setupLighting(); // 开始渲染循环 this.animate(); } setupLighting() { // 环境光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); this.scene.add(ambientLight); // 方向光 const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 15); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; this.scene.add(directionalLight); } animate() { requestAnimationFrame(() => this.animate()); this.renderer.render(this.scene, this.camera); } // 响应窗口大小变化 onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } }3.2 魔方模型创建
实现魔方管理器,负责生成可交互的3D魔方:
// js/core/CubeManager.js import * as THREE from 'three'; export class CubeManager { constructor(scene) { this.scene = scene; this.cubeGroup = new THREE.Group(); this.cubies = []; // 存储所有小方块 this.cubeSize = 3; // 3x3x3魔方 this.createCube(); } createCube() { const cubeletSize = 1; const gap = 0.05; // 方块间间隙 // 定义六个面的颜色 const colors = { front: 0xff0000, // 红 back: 0xff8800, // 橙 top: 0xffffff, // 白 bottom: 0xffff00, // 黄 left: 0x00ff00, // 绿 right: 0x0000ff // 蓝 }; // 创建3x3x3魔方 for (let x = -1; x <= 1; x++) { for (let y = -1; y <= 1; y++) { for (let z = -1; z <= 1; z++) { // 跳过中心块(不可见) if (x === 0 && y === 0 && z === 0) continue; const cubelet = this.createCubelet(x, y, z, cubeletSize, gap, colors); this.cubies.push(cubelet); this.cubeGroup.add(cubelet); } } } this.scene.add(this.cubeGroup); } createCubelet(x, y, z, size, gap, colors) { const group = new THREE.Group(); const geometry = new THREE.BoxGeometry(size - gap, size - gap, size - gap); // 创建六个面,但只显示外表面 const materials = []; const positions = [ { normal: new THREE.Vector3(0, 0, 1), color: colors.front }, // 前 { normal: new THREE.Vector3(0, 0, -1), color: colors.back }, // 后 { normal: new THREE.Vector3(0, 1, 0), color: colors.top }, // 上 { normal: new THREE.Vector3(0, -1, 0), color: colors.bottom }, // 下 { normal: new THREE.Vector3(-1, 0, 0), color: colors.left }, // 左 { normal: new THREE.Vector3(1, 0, 0), color: colors.right } // 右 ]; positions.forEach(pos => { const material = new THREE.MeshLambertMaterial({ color: this.shouldShowFace(x, y, z, pos.normal) ? pos.color : 0x444444 }); materials.push(material); }); const cube = new THREE.Mesh(geometry, materials); cube.position.set(x * size, y * size, z * size); cube.userData = { originalPosition: { x, y, z } }; group.add(cube); return group; } shouldShowFace(x, y, z, normal) { // 判断是否为外表面 return (x === -1 && normal.x === -1) || (x === 1 && normal.x === 1) || (y === -1 && normal.y === -1) || (y === 1 && normal.y === 1) || (z === -1 && normal.z === -1) || (z === 1 && normal.z === 1); } // 魔方旋转方法 rotateLayer(layer, axis, clockwise = true) { const rotationAxis = new THREE.Vector3(); switch (axis) { case 'x': rotationAxis.set(1, 0, 0); break; case 'y': rotationAxis.set(0, 1, 0); break; case 'z': rotationAxis.set(0, 0, 1); break; } const angle = clockwise ? Math.PI / 2 : -Math.PI / 2; const affectedCubies = this.getCubiesInLayer(layer, axis); affectedCubies.forEach(cubie => { cubie.rotateOnWorldAxis(rotationAxis, angle); }); } getCubiesInLayer(layer, axis) { return this.cubies.filter(cubie => { const pos = cubie.children[0].userData.originalPosition; switch (axis) { case 'x': return pos.x === layer; case 'y': return pos.y === layer; case 'z': return pos.z === layer; } }); } }4. 物理引擎集成与交互实现
4.1 Cannon.js物理系统设置
集成物理引擎,实现真实的物理效果:
// js/core/PhysicsManager.js import * as CANNON from 'cannon-es'; export class PhysicsManager { constructor() { this.world = new CANNON.World(); this.world.gravity.set(0, -9.82, 0); this.world.broadphase = new CANNON.NaiveBroadphase(); this.world.solver.iterations = 10; this.bodies = new Map(); // 存储物理体与3D对象的映射 this.setupGround(); } setupGround() { // 创建地面 const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0 }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); this.world.addBody(groundBody); } addPhysicsBody(mesh, mass = 1) { const shape = new CANNON.Box(new CANNON.Vec3( mesh.geometry.parameters.width / 2, mesh.geometry.parameters.height / 2, mesh.geometry.parameters.depth / 2 )); const body = new CANNON.Body({ mass }); body.addShape(shape); body.position.copy(mesh.position); body.quaternion.copy(mesh.quaternion); this.world.addBody(body); this.bodies.set(mesh, body); return body; } update() { this.world.step(1/60); // 同步物理体与3D对象的位置 this.bodies.forEach((body, mesh) => { mesh.position.copy(body.position); mesh.quaternion.copy(body.quaternion); }); } // 施加力模拟摇动效果 applyShakeForce(intensity = 10) { this.bodies.forEach((body, mesh) => { if (body.mass > 0) { const force = new CANNON.Vec3( (Math.random() - 0.5) * intensity, Math.random() * intensity, (Math.random() - 0.5) * intensity ); body.applyForce(force, body.position); } }); } }4.2 手势识别与交互控制
实现触摸屏手势识别,支持拧动和摇动操作:
// js/utils/GestureHandler.js import Hammer from 'hammerjs'; export class GestureHandler { constructor(element, cubeManager, physicsManager) { this.cubeManager = cubeManager; this.physicsManager = physicsManager; this.isRotating = false; this.setupGestures(element); } setupGestures(element) { const mc = new Hammer.Manager(element); // 旋转手势 const rotate = new Hammer.Rotate(); rotate.set({ enable: true }); mc.add(rotate); // 拖动手势 const pan = new Hammer.Pan(); pan.set({ direction: Hammer.DIRECTION_ALL }); mc.add(pan); // 缩放手势 const pinch = new Hammer.Pinch(); pinch.set({ enable: true }); mc.add(pinch); mc.on('rotatestart', (e) => this.onRotateStart(e)); mc.on('rotate', (e) => this.onRotate(e)); mc.on('rotateend', (e) => this.onRotateEnd(e)); mc.on('panstart', (e) => this.onPanStart(e)); mc.on('pan', (e) => this.onPan(e)); mc.on('panend', (e) => this.onPanEnd(e)); // 摇动手势检测(通过加速度传感器) if (window.DeviceMotionEvent) { window.addEventListener('devicemotion', (e) => this.handleShake(e)); } } onRotateStart(e) { this.isRotating = true; this.startRotation = e.rotation; } onRotate(e) { if (!this.isRotating) return; const deltaRotation = e.rotation - this.startRotation; const rotationSpeed = deltaRotation * 0.01; // 根据旋转方向拧动魔方层 if (Math.abs(deltaRotation) > 30) { const layer = Math.floor(Math.random() * 3) - 1; // -1, 0, 1 const axis = ['x', 'y', 'z'][Math.floor(Math.random() * 3)]; const clockwise = deltaRotation > 0; this.cubeManager.rotateLayer(layer, axis, clockwise); this.startRotation = e.rotation; } } onRotateEnd(e) { this.isRotating = false; } handleShake(e) { const acceleration = e.accelerationIncludingGravity; if (!acceleration) return; const shakeThreshold = 15; const totalAcceleration = Math.abs(acceleration.x) + Math.abs(acceleration.y) + Math.abs(acceleration.z); if (totalAcceleration > shakeThreshold) { this.physicsManager.applyShakeForce(totalAcceleration / 2); } } // 茶壶拧动特效 twistTeapot(direction) { // 模拟茶壶拧动动画 const twistAnimation = { duration: 0.5, rotation: direction === 'clockwise' ? Math.PI / 4 : -Math.PI / 4, onComplete: () => this.resetTwist() }; // 这里可以添加茶壶模型的旋转动画 console.log(`茶壶${direction === 'clockwise' ? '顺时针' : '逆时针'}拧动`); } resetTwist() { // 复位茶壶位置 console.log('茶壶复位'); } }5. 茶艺元素集成与动画效果
5.1 茶壶模型创建与动画
创建茶壶3D模型并实现拧动动画:
// js/core/TeapotManager.js import * as THREE from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; export class TeapotManager { constructor(scene) { this.scene = scene; this.teapot = null; this.isTwisting = false; this.loadTeapotModel(); } loadTeapotModel() { const loader = new GLTFLoader(); // 可以使用在线模型或本地模型 loader.load('assets/models/teapot.glb', (gltf) => { this.teapot = gltf.scene; this.teapot.scale.set(0.5, 0.5, 0.5); this.teapot.position.set(3, 1, 0); // 设置茶壶材质和阴影 this.teapot.traverse((child) => { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; } }); this.scene.add(this.teapot); }, undefined, (error) => { console.error('茶壶模型加载失败:', error); this.createFallbackTeapot(); }); } createFallbackTeapot() { // 备用方案:使用基本几何体创建茶壶 const teapotGroup = new THREE.Group(); // 壶身 const bodyGeometry = new THREE.CylinderGeometry(1, 0.8, 1.5, 32); const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x8B4513 }); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); body.castShadow = true; // 壶嘴 const spoutGeometry = new THREE.ConeGeometry(0.1, 0.8, 8); const spout = new THREE.Mesh(spoutGeometry, bodyMaterial); spout.position.set(0.7, 0.3, 0); spout.rotation.z = -Math.PI / 6; // 壶把 const handleGeometry = new THREE.TorusGeometry(0.4, 0.05, 8, 32); const handle = new THREE.Mesh(handleGeometry, bodyMaterial); handle.position.set(-0.6, 0.5, 0); handle.rotation.z = Math.PI / 2; teapotGroup.add(body); teapotGroup.add(spout); teapotGroup.add(handle); teapotGroup.position.set(3, 1, 0); this.teapot = teapotGroup; this.scene.add(this.teapot); } // 茶壶拧动动画 twist(clockwise = true) { if (this.isTwisting || !this.teapot) return; this.isTwisting = true; const twistAngle = clockwise ? Math.PI / 3 : -Math.PI / 3; const originalRotation = this.teapot.rotation.y; // 使用GSAP创建平滑动画 const twistTween = { rotation: originalRotation + twistAngle, duration: 0.3, ease: "power2.inOut", onComplete: () => { setTimeout(() => this.untwist(originalRotation), 200); } }; // 实际项目中这里使用GSAP this.animateTwist(twistTween); } animateTwist(tween) { const startTime = Date.now(); const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / (tween.duration * 1000), 1); if (progress < 1) { this.teapot.rotation.y = this.easeInOut(progress) * tween.rotation; requestAnimationFrame(animate); } else { this.teapot.rotation.y = tween.rotation; tween.onComplete && tween.onComplete(); } }; animate(); } easeInOut(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; } untwist(originalRotation) { const untwistTween = { rotation: originalRotation, duration: 0.2, ease: "power2.out", onComplete: () => { this.isTwisting = false; } }; this.animateTwist(untwistTween); } }5.2 粒子效果与视觉反馈
添加茶水和互动粒子效果:
// js/effects/ParticleSystem.js import * as THREE from 'three'; export class ParticleSystem { constructor(scene) { this.scene = scene; this.particles = []; this.particleGroup = new THREE.Group(); this.scene.add(this.particleGroup); } createTeaSteam(position) { const particleCount = 20; const geometry = new THREE.BufferGeometry(); const positions = new Float32Array(particleCount * 3); const sizes = new Float32Array(particleCount); for (let i = 0; i < particleCount; i++) { positions[i * 3] = position.x + (Math.random() - 0.5) * 0.5; positions[i * 3 + 1] = position.y + Math.random() * 2; positions[i * 3 + 2] = position.z + (Math.random() - 0.5) * 0.5; sizes[i] = Math.random() * 0.5 + 0.1; } geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); const material = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1, transparent: true, opacity: 0.6 }); const particles = new THREE.Points(geometry, material); this.particleGroup.add(particles); this.particles.push({ mesh: particles, startTime: Date.now(), duration: 3000 // 3秒 }); } update() { const currentTime = Date.now(); for (let i = this.particles.length - 1; i >= 0; i--) { const particle = this.particles[i]; const elapsed = currentTime - particle.startTime; const progress = elapsed / particle.duration; if (progress >= 1) { this.particleGroup.remove(particle.mesh); this.particles.splice(i, 1); } else { // 更新粒子位置和透明度 particle.mesh.position.y += 0.01; particle.mesh.material.opacity = 0.6 * (1 - progress); } } } createMagicSparkles(position, count = 50) { // 创建魔方旋转时的魔法火花效果 const geometry = new THREE.BufferGeometry(); const positions = new Float32Array(count * 3); const colors = new Float32Array(count * 3); for (let i = 0; i < count; i++) { positions[i * 3] = position.x + (Math.random() - 0.5) * 2; positions[i * 3 + 1] = position.y + (Math.random() - 0.5) * 2; positions[i * 3 + 2] = position.z + (Math.random() - 0.5) * 2; // 随机颜色 colors[i * 3] = Math.random(); colors[i * 3 + 1] = Math.random(); colors[i * 3 + 2] = Math.random(); } geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); const material = new THREE.PointsMaterial({ size: 0.1, vertexColors: true, transparent: true, opacity: 0.8 }); const sparkles = new THREE.Points(geometry, material); this.particleGroup.add(sparkles); this.particles.push({ mesh: sparkles, startTime: currentTime, duration: 2000 }); } }6. 主程序集成与用户体验优化
6.1 应用入口文件整合
将各个模块整合到主程序中:
// js/main.js import { SceneManager } from './core/SceneManager.js'; import { CubeManager } from './core/CubeManager.js'; import { TeapotManager } from './core/TeapotManager.js'; import { PhysicsManager } from './core/PhysicsManager.js'; import { GestureHandler } from './utils/GestureHandler.js'; import { ParticleSystem } from './effects/ParticleSystem.js'; class MagicCubeTeaApp { constructor() { this.init(); } init() { // 初始化场景 this.sceneManager = new SceneManager('canvas-container'); this.physicsManager = new PhysicsManager(); this.particleSystem = new ParticleSystem(this.sceneManager.scene); // 创建魔方和茶壶 this.cubeManager = new CubeManager(this.sceneManager.scene); this.teapotManager = new TeapotManager(this.sceneManager.scene); // 设置手势交互 this.gestureHandler = new GestureHandler( this.sceneManager.renderer.domElement, this.cubeManager, this.physicsManager ); // 绑定UI事件 this.setupUIEvents(); // 启动主循环 this.animate(); // 响应窗口大小变化 window.addEventListener('resize', () => this.onWindowResize()); } setupUIEvents() { document.getElementById('shake-btn').addEventListener('click', () => { this.shakeCube(); }); document.getElementById('twist-btn').addEventListener('click', () => { this.twistTeapot(); }); // 键盘快捷键 document.addEventListener('keydown', (e) => { switch(e.code) { case 'Space': this.shakeCube(); break; case 'KeyT': this.twistTeapot(); break; } }); } shakeCube() { this.physicsManager.applyShakeForce(15); this.particleSystem.createMagicSparkles( this.cubeManager.cubeGroup.position ); } twistTeapot() { this.teapotManager.twist(Math.random() > 0.5); this.particleSystem.createTeaSteam( this.teapotManager.teapot.position ); } animate() { requestAnimationFrame(() => this.animate()); // 更新物理世界 this.physicsManager.update(); // 更新粒子系统 this.particleSystem.update(); // 更新渲染 this.sceneManager.renderer.render( this.sceneManager.scene, this.sceneManager.camera ); } onWindowResize() { this.sceneManager.onWindowResize(); } } // 启动应用 new MagicCubeTeaApp();6.2 响应式样式设计
添加CSS样式确保在不同设备上都有良好的显示效果:
/* css/style.css */ body { margin: 0; padding: 0; font-family: 'Microsoft YaHei', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); overflow: hidden; } #container { position: relative; width: 100vw; height: 100vh; } #canvas-container { width: 100%; height: 100%; } #ui-controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 20px; z-index: 100; } button { padding: 12px 24px; background: rgba(255, 255, 255, 0.9); border: none; border-radius: 25px; font-size: 16px; font-weight: bold; color: #333; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); transition: all 0.3s ease; } button:hover { background: rgba(255, 255, 255, 1); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); } button:active { transform: translateY(0); } #gesture-hint { background: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 20px; font-size: 14px; } /* 移动端适配 */ @media (max-width: 768px) { #ui-controls { flex-direction: column; align-items: center; } button { padding: 15px 30px; font-size: 18px; } #gesture-hint { font-size: 12px; text-align: center; } } /* 加载动画 */ .loading { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 24px; z-index: 1000; }7. 性能优化与最佳实践
7.1 3D渲染性能优化
针对WebGL渲染进行性能调优:
// js/utils/PerformanceOptimizer.js export class PerformanceOptimizer { static optimizeRenderer(renderer) { // 设置合理的像素比例 renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 启用纹理压缩(如果支持) if (renderer.extensions.get('WEBGL_compressed_texture')) { // 配置纹理压缩格式 } } static optimizeScene(scene) { // 合并几何体减少draw call this.mergeGeometries(scene); // 设置适当的LOD(细节层次) this.setupLOD(scene); } static mergeGeometries(scene) { // 将相同材质的几何体合并 const geometryGroups = new Map(); scene.traverse((object) => { if (object.isMesh) { const key = object.material.uuid; if (!geometryGroups.has(key)) { geometryGroups.set(key, []); } geometryGroups.get(key).push(object); } }); geometryGroups.forEach((meshes, materialId) => { if (meshes.length > 1) { this.mergeMeshGroup(meshes); } }); } static setupLOD(scene) { // 为远距离对象设置低细节模型 scene.traverse((object) => { if (object.isMesh && object.geometry) { // 根据对象大小和重要性设置LOD const boundingBox = new THREE.Box3().setFromObject(object); const size = boundingBox.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); if (maxDim < 1) { // 小对象使用较低细节 object.geometry = this.simplifyGeometry(object.geometry, 0.5); } } }); } }7.2 内存管理与资源清理
确保应用不会内存泄漏:
// js/utils/MemoryManager.js export class MemoryManager { constructor() { this.resources = new Set(); } trackResource(resource) { this.resources.add(resource); return resource; } disposeResource(resource) { if (resource.dispose) { resource.dispose(); } if (resource.geometry) { resource.geometry.dispose(); } if (resource.material) { if (Array.isArray(resource.material)) { resource.material.forEach(material => material.dispose()); } else { resource.material.dispose(); } } if (resource.texture) { resource.texture.dispose(); } this.resources.delete(resource); } cleanup() { this.resources.forEach(resource => this.disposeResource(resource)); this.resources.clear(); } // 监控内存使用 monitorMemory() { if (performance.memory) { const usedMB = performance.memory.usedJSHeapSize / 1048576; const limitMB = performance.memory.jsHeapSizeLimit / 1048576; if (usedMB > limitMB * 0.8) { console.warn('内存使用过高,建议清理资源'); this.cleanupUnusedResources(); } } } cleanupUnusedResources() { // 清理长时间未使用的资源 const now = Date.now(); this.resources.forEach(resource => { if (now - resource.lastUsed > 30000) { // 30秒未使用 this.disposeResource(resource); } }); } }8. 常见问题与解决方案
8.1 性能问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 页面卡顿,帧率低 | 1. 图形复杂度太高 2. 物理计算过多 3. 内存泄漏 | 1. 减少多边形数量 2. 优化物理参数 3. 使用性能分析工具 |
| 移动端发热严重 | 1. 连续渲染未优化 2. 计算密集型操作 | 1. 添加渲染暂停机制 2. 降低渲染质量 |
| 加载时间过长 | 1. 模型文件太大 2. 资源未压缩 | 1. 使用GLTF压缩 2. 实现渐进式加载 |