<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D产品展示系统</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.156.1/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.156.1/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.156.1/examples/js/loaders/GLTFLoader.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
overflow: hidden;
}
#app {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
background: rgba(255, 255, 255, 0.95);
padding: 20px 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 24px;
color: #333;
margin-bottom: 5px;
}
.header p {
color: #666;
font-size: 14px;
}
.main-content {
flex: 1;
display: flex;
padding: 20px;
gap: 20px;
overflow: hidden;
}
.viewer-container {
flex: 1;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
}
#canvas-container {
width: 100%;
height: 100%;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 10;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #666;
font-size: 16px;
}
.controls-panel {
width: 320px;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 25px;
overflow-y: auto;
}
.control-section {
margin-bottom: 30px;
}
.control-section h3 {
font-size: 16px;
color: #333;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
}
.control-item {
margin-bottom: 20px;
}
.control-item label {
display: block;
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.color-options {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.color-btn {
width: 100%;
aspect-ratio: 1;
border: 3px solid transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.color-btn:hover {
transform: scale(1.1);
}
.color-btn.active {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.3);
}
.slider-container {
display: flex;
align-items: center;
gap: 12px;
}
.slider {
flex: 1;
height: 6px;
border-radius: 3px;
background: #e0e0e0;
outline: none;
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
transition: all 0.3s;
}
.slider::-webkit-slider-thumb:hover {
background: #764ba2;
transform: scale(1.2);
}
.slider-value {
min-width: 40px;
text-align: right;
color: #333;
font-weight: 600;
font-size: 14px;
}
.btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 10px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #f5f5f5;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.stats {
position: absolute;
bottom: 15px;
left: 15px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 15px;
border-radius: 8px;
font-size: 12px;
font-family: 'Courier New', monospace;
}
.stats div {
margin-bottom: 5px;
}
.stats div:last-child {
margin-bottom: 0;
}
</style>
</head>
<body>
<div id="app">
<div class="header">
<h1>3D产品展示系统</h1>
<p>支持360度旋转、材质切换、灯光调节等交互功能</p>
</div>
<div class="main-content">
<div class="viewer-container">
<div id="canvas-container"></div>
<div v-if="loading" class="loading">
<div class="loading-spinner"></div>
<div class="loading-text">加载中... {{ loadingProgress }}%</div>
</div>
<div class="stats" v-if="showStats">
<div>FPS: {{ fps }}</div>
<div>三角面: {{ triangles }}</div>
<div>Draw Calls: {{ drawCalls }}</div>
</div>
</div>
<div class="controls-panel">
<div class="control-section">
<h3>产品颜色</h3>
<div class="color-options">
<button
v-for="color in colors"
:key="color.name"
class="color-btn"
:class="{ active: selectedColor === color.name }"
:style="{ background: color.value }"
@click="changeColor(color)"
:title="color.name"
></button>
</div>
</div>
<div class="control-section">
<h3>灯光设置</h3>
<div class="control-item">
<label>环境光强度</label>
<div class="slider-container">
<input
type="range"
class="slider"
v-model="ambientIntensity"
min="0"
max="2"
step="0.1"
@input="updateLighting"
>
<span class="slider-value">{{ ambientIntensity }}</span>
</div>
</div>
<div class="control-item">
<label>点光源强度</label>
<div class="slider-container">
<input
type="range"
class="slider"
v-model="pointIntensity"
min="0"
max="3"
step="0.1"
@input="updateLighting"
>
<span class="slider-value">{{ pointIntensity }}</span>
</div>
</div>
</div>
<div class="control-section">
<h3>场景控制</h3>
<button class="btn btn-primary" @click="toggleRotation">
{{ autoRotate ? '停止旋转' : '自动旋转' }}
</button>
<button class="btn btn-secondary" @click="resetCamera">
重置视角
</button>
<button class="btn btn-secondary" @click="toggleStats">
{{ showStats ? '隐藏' : '显示' }}性能统计
</button>
</div>
<div class="control-section">
<h3>关于</h3>
<p style="color: #666; font-size: 13px; line-height: 1.6;">
这是一个基于Three.js的3D产品展示系统示例。支持产品360度查看、材质实时切换、灯光调节等功能。
</p>
</div>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
const { Scene, PerspectiveCamera, WebGLRenderer, AmbientLight, PointLight,
MeshStandardMaterial, BoxGeometry, Mesh, Color } = THREE;
createApp({
data() {
return {
loading: true,
loadingProgress: 0,
scene: null,
camera: null,
renderer: null,
controls: null,
product: null,
ambientLight: null,
pointLight: null,
animationId: null,
// 控制参数
colors: [
{ name: '黑色', value: '#2c3e50' },
{ name: '白色', value: '#ecf0f1' },
{ name: '蓝色', value: '#3498db' },
{ name: '红色', value: '#e74c3c' },
{ name: '绿色', value: '#2ecc71' },
{ name: '黄色', value: '#f39c12' },
{ name: '紫色', value: '#9b59b6' },
{ name: '粉色', value: '#fd79a8' }
],
selectedColor: '黑色',
ambientIntensity: 0.8,
pointIntensity: 1.5,
autoRotate: true,
showStats: true,
// 性能统计
fps: 0,
triangles: 0,
drawCalls: 0,
lastTime: performance.now(),
frames: 0
};
},
mounted() {
this.initScene();
this.createProduct();
this.animate();
window.addEventListener('resize', this.onWindowResize);
// 模拟加载进度
const progressInterval = setInterval(() => {
this.loadingProgress += 10;
if (this.loadingProgress >= 100) {
this.loadingProgress = 100;
setTimeout(() => {
this.loading = false;
}, 300);
clearInterval(progressInterval);
}
}, 100);
},
beforeUnmount() {
window.removeEventListener('resize', this.onWindowResize);
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
this.dispose();
},
methods: {
initScene() {
const container = document.getElementById('canvas-container');
const width = container.clientWidth;
const height = container.clientHeight;
// 创建场景
this.scene = new Scene();
this.scene.background = new Color(0xf0f0f0);
// 创建相机
this.camera = new PerspectiveCamera(45, width / height, 0.1, 1000);
this.camera.position.set(5, 3, 5);
this.camera.lookAt(0, 0, 0);
// 创建渲染器
this.renderer = new WebGLRenderer({ antialias: true });
this.renderer.setSize(width, height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.shadowMap.enabled = true;
container.appendChild(this.renderer.domElement);
// 创建控制器
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
this.controls.autoRotate = this.autoRotate;
this.controls.autoRotateSpeed = 2;
// 创建灯光
this.ambientLight = new AmbientLight(0xffffff, this.ambientIntensity);
this.scene.add(this.ambientLight);
this.pointLight = new PointLight(0xffffff, this.pointIntensity, 100);
this.pointLight.position.set(5, 5, 5);
this.pointLight.castShadow = true;
this.scene.add(this.pointLight);
// 添加第二个补光
const fillLight = new PointLight(0xffffff, 0.5, 100);
fillLight.position.set(-5, 3, -5);
this.scene.add(fillLight);
},
createProduct() {
// 创建产品模型 - 这里用简单的几何体模拟手机
const phoneGroup = new THREE.Group();
// 手机主体
const bodyGeometry = new BoxGeometry(1, 2, 0.1);
const bodyMaterial = new MeshStandardMaterial({
color: 0x2c3e50,
metalness: 0.7,
roughness: 0.3
});
const body = new Mesh(bodyGeometry, bodyMaterial);
body.castShadow = true;
body.receiveShadow = true;
phoneGroup.add(body);
// 屏幕
const screenGeometry = new BoxGeometry(0.9, 1.8, 0.02);
const screenMaterial = new MeshStandardMaterial({
color: 0x1a1a1a,
metalness: 0.9,
roughness: 0.1
});
const screen = new Mesh(screenGeometry, screenMaterial);
screen.position.z = 0.06;
phoneGroup.add(screen);
// 摄像头
const cameraGeometry = new THREE.CylinderGeometry(0.08, 0.08, 0.05, 32);
const cameraMaterial = new MeshStandardMaterial({
color: 0x111111,
metalness: 0.8,
roughness: 0.2
});
const camera = new Mesh(cameraGeometry, cameraMaterial);
camera.rotation.x = Math.PI / 2;
camera.position.set(-0.3, 0.7, -0.08);
phoneGroup.add(camera);
this.product = phoneGroup;
this.product.userData.bodyMaterial = bodyMaterial;
this.scene.add(phoneGroup);
},
changeColor(color) {
this.selectedColor = color.name;
if (this.product && this.product.userData.bodyMaterial) {
this.product.userData.bodyMaterial.color.set(color.value);
}
},
updateLighting() {
if (this.ambientLight) {
this.ambientLight.intensity = parseFloat(this.ambientIntensity);
}
if (this.pointLight) {
this.pointLight.intensity = parseFloat(this.pointIntensity);
}
},
toggleRotation() {
this.autoRotate = !this.autoRotate;
if (this.controls) {
this.controls.autoRotate = this.autoRotate;
}
},
resetCamera() {
if (this.camera && this.controls) {
this.camera.position.set(5, 3, 5);
this.controls.target.set(0, 0, 0);
this.controls.update();
}
},
toggleStats() {
this.showStats = !this.showStats;
},
updateStats() {
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;
if (this.renderer && this.scene && this.camera) {
const info = this.renderer.info;
this.triangles = info.render.triangles;
this.drawCalls = info.render.calls;
}
}
},
animate() {
this.animationId = requestAnimationFrame(this.animate);
if (this.controls) {
this.controls.update();
}
if (this.renderer && this.scene && this.camera) {
this.renderer.render(this.scene, this.camera);
}
this.updateStats();
},
onWindowResize() {
const container = document.getElementById('canvas-container');
if (!container) return;
const width = container.clientWidth;
const height = container.clientHeight;
if (this.camera) {
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
}
if (this.renderer) {
this.renderer.setSize(width, height);
}
},
dispose() {
// 清理资源
if (this.scene) {
this.scene.traverse((object) => {
if (object.geometry) {
object.geometry.dispose();
}
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach(material => material.dispose());
} else {
object.material.dispose();
}
}
});
}
if (this.renderer) {
this.renderer.dispose();
}
if (this.controls) {
this.controls.dispose();
}
}
}
}).mount('#app');
</script>
</body>
</html>
返回笔记首页
3D产品展示系统
主题配置