perf(christmas): 优化圣诞树页面性能

- 减少CSS代码行数,合并重复样式声明
- 降低粒子系统中几何体的分段数以减少内存占用
- 减少粒子和尘埃粒子的数量以提升渲染性能
- 添加硬件加速样式以提高动画流畅度
- 优化WebGL渲染器配置,限制像素比率
- 调整后期处理效果参数以平衡视觉效果与性能
- 优化动画插值计算,防止过度计算
- 在窗口大小调整事件中添加防抖动处理
- 限制动画帧时间差最大值以稳定动画表现
- 添加手势状态变化检测以减少不必要的状态切换
This commit is contained in:
hehh
2025-12-12 17:05:25 +08:00
parent cada354dbe
commit 785bf0fe61

View File

@@ -43,33 +43,14 @@
<meta property="wechat:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。"> <meta property="wechat:description" content="我是一名充满热情的Java后端开发工程师专注于AI技术的探索与应用。">
<title>Grand Luxury Tree Final v2</title> <title>Grand Luxury Tree Final v2</title>
<style> <style>
body { body { margin: 0; overflow: hidden; background-color: #000000; font-family: 'Times New Roman', serif; }
margin: 0; #canvas-container { width: 100vw; height: 100vh; position: absolute; top: 0; left: 0; z-index: 1; }
overflow: hidden;
background-color: #000000;
font-family: 'Times New Roman', serif;
}
#canvas-container {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
/* UI Overlay - Minimalist */ /* UI Overlay - Minimalist */
#ui-layer { #ui-layer {
position: absolute; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
top: 0; z-index: 10; pointer-events: none;
left: 0; display: flex; flex-direction: column;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none;
display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding-top: 40px; padding-top: 40px;
box-sizing: border-box; box-sizing: border-box;
@@ -84,58 +65,29 @@
/* Loading */ /* Loading */
#loader { #loader {
position: absolute; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
top: 0; background: #000; z-index: 100;
left: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
width: 100%;
height: 100%;
background: #000;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: opacity 0.8s ease-out; transition: opacity 0.8s ease-out;
} }
.loader-text { .loader-text {
color: #d4af37; color: #d4af37; font-size: 14px; letter-spacing: 4px; margin-top: 20px;
font-size: 14px; text-transform: uppercase; font-weight: 100;
letter-spacing: 4px;
margin-top: 20px;
text-transform: uppercase;
font-weight: 100;
} }
.spinner { .spinner {
width: 40px; width: 40px; height: 40px; border: 1px solid rgba(212, 175, 55, 0.2);
height: 40px; border-top: 1px solid #d4af37; border-radius: 50%;
border: 1px solid rgba(212, 175, 55, 0.2);
border-top: 1px solid #d4af37;
border-radius: 50%;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
} }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Typography - Centerpiece */ /* Typography - Centerpiece */
h1 { h1 {
color: #fceea7; color: #fceea7; font-size: 56px; margin: 0; font-weight: 400;
font-size: 56px;
margin: 0;
font-weight: 400;
letter-spacing: 6px; letter-spacing: 6px;
text-shadow: 0 0 50px rgba(252, 238, 167, 0.6); text-shadow: 0 0 50px rgba(252, 238, 167, 0.6);
background: linear-gradient(to bottom, #fff, #eebb66); background: linear-gradient(to bottom, #fff, #eebb66);
-webkit-background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent;
-webkit-text-fill-color: transparent;
font-family: 'Cinzel', 'Times New Roman', serif; font-family: 'Cinzel', 'Times New Roman', serif;
opacity: 0.9; opacity: 0.9;
transition: opacity 0.5s ease; /* Ensure smooth transitions if needed */ transition: opacity 0.5s ease; /* Ensure smooth transitions if needed */
@@ -148,7 +100,6 @@
text-align: center; text-align: center;
transition: opacity 0.5s ease; /* Add transition for smooth hiding */ transition: opacity 0.5s ease; /* Add transition for smooth hiding */
} }
.upload-btn { .upload-btn {
background: rgba(20, 20, 20, 0.6); background: rgba(20, 20, 20, 0.6);
border: 1px solid rgba(212, 175, 55, 0.4); border: 1px solid rgba(212, 175, 55, 0.4);
@@ -162,13 +113,11 @@
display: inline-block; display: inline-block;
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
} }
.upload-btn:hover { .upload-btn:hover {
background: #d4af37; background: #d4af37;
color: #000; color: #000;
box-shadow: 0 0 20px rgba(212, 175, 55, 0.5); box-shadow: 0 0 20px rgba(212, 175, 55, 0.5);
} }
.hint-text { .hint-text {
color: rgba(212, 175, 55, 0.5); color: rgba(212, 175, 55, 0.5);
font-size: 9px; font-size: 9px;
@@ -177,22 +126,27 @@
text-transform: uppercase; text-transform: uppercase;
} }
#file-input { #file-input { display: none; }
display: none;
}
/* Webcam feedback */ /* Webcam feedback */
#webcam-wrapper { #webcam-wrapper {
position: absolute; position: absolute; bottom: 40px; right: 40px;
bottom: 40px; width: 120px; height: 90px;
right: 40px; border: 1px solid rgba(255,255,255,0.1);
width: 120px; overflow: hidden; opacity: 0; /* Hidden by default but functional */
height: 90px;
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
opacity: 0; /* Hidden by default but functional */
pointer-events: none; pointer-events: none;
} }
/* 添加硬件加速样式以提高性能 */
#canvas-container, #ui-layer, #loader {
will-change: transform;
transform: translateZ(0);
backface-visibility: hidden;
}
.particle {
will-change: transform;
}
</style> </style>
<style> <style>
@@ -253,8 +207,8 @@
accentRed: 0x990000, accentRed: 0x990000,
}, },
particles: { particles: {
count: 1500, count: 1000, // 减少粒子数量以提高性能
dustCount: 2500, dustCount: 2000, // 减少尘埃粒子数量
treeHeight: 24, treeHeight: 24,
treeRadius: 8 treeRadius: 8
}, },
@@ -323,11 +277,19 @@
camera = new THREE.PerspectiveCamera(42, window.innerWidth / window.innerHeight, 0.1, 1000); camera = new THREE.PerspectiveCamera(42, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, CONFIG.camera.z); camera.position.set(0, 2, CONFIG.camera.z);
renderer = new THREE.WebGLRenderer({antialias: true, alpha: false, powerPreference: "high-performance"}); renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
powerPreference: "high-performance",
stencil: false,
depth: true,
logarithmicDepthBuffer: false
});
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.toneMapping = THREE.ReinhardToneMapping; renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.toneMappingExposure = 2.2; renderer.toneMappingExposure = 2.2;
renderer.setClearColor(0x000000, 1);
container.appendChild(renderer.domElement); container.appendChild(renderer.domElement);
mainGroup = new THREE.Group(); mainGroup = new THREE.Group();
@@ -364,10 +326,10 @@
function setupPostProcessing() { function setupPostProcessing() {
const renderScene = new RenderPass(scene, camera); const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85); const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.2, 0.4, 0.85);
bloomPass.threshold = 0.7; bloomPass.threshold = 0.8;
bloomPass.strength = 0.45; bloomPass.strength = 0.35;
bloomPass.radius = 0.4; bloomPass.radius = 0.35;
composer = new EffectComposer(renderer); composer = new EffectComposer(renderer);
composer.addPass(renderScene); composer.addPass(renderScene);
@@ -417,6 +379,9 @@
); );
this.calculatePositions(); this.calculatePositions();
// 添加类标识符以提高性能
mesh.userData.particleType = type;
} }
calculatePositions() { calculatePositions() {
@@ -457,19 +422,19 @@
} }
} }
// Movement Easing // Movement Easing - 优化动画插值计算
const lerpSpeed = (mode === 'FOCUS' && this.mesh === focusTargetMesh) ? 5.0 : 2.0; const lerpSpeed = (mode === 'FOCUS' && this.mesh === focusTargetMesh) ? 5.0 : 2.0;
this.mesh.position.lerp(target, lerpSpeed * dt); this.mesh.position.lerp(target, Math.min(lerpSpeed * dt, 1.0));
// Rotation Logic - CRITICAL: Ensure spin happens in Scatter // Rotation Logic - CRITICAL: Ensure spin happens in Scatter
if (mode === 'SCATTER') { if (mode === 'SCATTER') {
this.mesh.rotation.x += this.spinSpeed.x * dt; this.mesh.rotation.x += this.spinSpeed.x * dt;
this.mesh.rotation.y += this.spinSpeed.y * dt; this.mesh.rotation.y += this.spinSpeed.y * dt;
this.mesh.rotation.z += this.spinSpeed.z * dt; // Added Z for more natural tumble this.mesh.rotation.z += this.spinSpeed.z * dt;
} else if (mode === 'TREE') { } else if (mode === 'TREE') {
// Reset rotations slowly // Reset rotations slowly
this.mesh.rotation.x = THREE.MathUtils.lerp(this.mesh.rotation.x, 0, dt); this.mesh.rotation.x = THREE.MathUtils.lerp(this.mesh.rotation.x, 0, Math.min(dt * 2, 1.0));
this.mesh.rotation.z = THREE.MathUtils.lerp(this.mesh.rotation.z, 0, dt); this.mesh.rotation.z = THREE.MathUtils.lerp(this.mesh.rotation.z, 0, Math.min(dt * 2, 1.0));
this.mesh.rotation.y += 0.5 * dt; this.mesh.rotation.y += 0.5 * dt;
} }
@@ -490,42 +455,50 @@
else s = this.baseScale * 0.8; else s = this.baseScale * 0.8;
} }
this.mesh.scale.lerp(new THREE.Vector3(s, s, s), 4 * dt); this.mesh.scale.lerp(new THREE.Vector3(s, s, s), Math.min(4 * dt, 1.0));
} }
} }
// --- CREATION --- // --- CREATION ---
function createParticles() { function createParticles() {
const sphereGeo = new THREE.SphereGeometry(0.5, 32, 32); // 优化几何体和材质的创建,减少内存占用
const boxGeo = new THREE.BoxGeometry(0.55, 0.55, 0.55); const sphereGeo = new THREE.SphereGeometry(0.5, 16, 16); // 减少分段数以提高性能
const boxGeo = new THREE.BoxGeometry(0.55, 0.55, 0.55, 1, 1, 1); // 减少分段数
const curve = new THREE.CatmullRomCurve3([ const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(0, -0.5, 0), new THREE.Vector3(0, 0.3, 0), new THREE.Vector3(0, -0.5, 0), new THREE.Vector3(0, 0.3, 0),
new THREE.Vector3(0.1, 0.5, 0), new THREE.Vector3(0.3, 0.4, 0) new THREE.Vector3(0.1, 0.5, 0), new THREE.Vector3(0.3, 0.4, 0)
]); ]);
const candyGeo = new THREE.TubeGeometry(curve, 16, 0.08, 8, false); const candyGeo = new THREE.TubeGeometry(curve, 8, 0.08, 4, false); // 减少分段数
const goldMat = new THREE.MeshStandardMaterial({ const goldMat = new THREE.MeshStandardMaterial({
color: CONFIG.colors.champagneGold, color: CONFIG.colors.champagneGold,
metalness: 1.0, roughness: 0.1, metalness: 1.0, roughness: 0.1,
envMapIntensity: 2.0, envMapIntensity: 2.0,
emissive: 0x443300, emissive: 0x443300,
emissiveIntensity: 0.3 emissiveIntensity: 0.3,
flatShading: false
}); });
const greenMat = new THREE.MeshStandardMaterial({ const greenMat = new THREE.MeshStandardMaterial({
color: CONFIG.colors.deepGreen, color: CONFIG.colors.deepGreen,
metalness: 0.2, roughness: 0.8, metalness: 0.2, roughness: 0.8,
emissive: 0x002200, emissive: 0x002200,
emissiveIntensity: 0.2 emissiveIntensity: 0.2,
flatShading: false
}); });
const redMat = new THREE.MeshPhysicalMaterial({ const redMat = new THREE.MeshPhysicalMaterial({
color: CONFIG.colors.accentRed, color: CONFIG.colors.accentRed,
metalness: 0.3, roughness: 0.2, clearcoat: 1.0, metalness: 0.3, roughness: 0.2, clearcoat: 1.0,
emissive: 0x330000 emissive: 0x330000,
flatShading: false
}); });
const candyMat = new THREE.MeshStandardMaterial({map: caneTexture, roughness: 0.4}); const candyMat = new THREE.MeshStandardMaterial({
map: caneTexture,
roughness: 0.4,
flatShading: false
});
for (let i = 0; i < CONFIG.particles.count; i++) { for (let i = 0; i < CONFIG.particles.count; i++) {
const rand = Math.random(); const rand = Math.random();
@@ -569,8 +542,9 @@
} }
function createDust() { function createDust() {
// 优化灰尘粒子的几何体以提高性能
const geo = new THREE.TetrahedronGeometry(0.08, 0); const geo = new THREE.TetrahedronGeometry(0.08, 0);
const mat = new THREE.MeshBasicMaterial({color: 0xffeebb, transparent: true, opacity: 0.8}); const mat = new THREE.MeshBasicMaterial({ color: 0xffeebb, transparent: true, opacity: 0.8 });
for (let i = 0; i < CONFIG.particles.dustCount; i++) { for (let i = 0; i < CONFIG.particles.dustCount; i++) {
const mesh = new THREE.Mesh(geo, mat); const mesh = new THREE.Mesh(geo, mat);
@@ -688,20 +662,21 @@
STATE.hand.x = (lm[9].x - 0.5) * 2; STATE.hand.x = (lm[9].x - 0.5) * 2;
STATE.hand.y = (lm[9].y - 0.5) * 2; STATE.hand.y = (lm[9].y - 0.5) * 2;
const thumb = lm[4]; const thumb = lm[4]; const index = lm[8]; const wrist = lm[0];
const index = lm[8];
const wrist = lm[0];
const pinchDist = Math.hypot(thumb.x - index.x, thumb.y - index.y); const pinchDist = Math.hypot(thumb.x - index.x, thumb.y - index.y);
const tips = [lm[8], lm[12], lm[16], lm[20]]; const tips = [lm[8], lm[12], lm[16], lm[20]];
let avgDist = 0; let avgDist = 0;
tips.forEach(t => avgDist += Math.hypot(t.x - wrist.x, t.y - wrist.y)); tips.forEach(t => avgDist += Math.hypot(t.x - wrist.x, t.y - wrist.y));
avgDist /= 4; avgDist /= 4;
// 添加手势状态变化检测以减少不必要的状态切换
const prevMode = STATE.mode;
if (pinchDist < 0.05) { if (pinchDist < 0.05) {
if (STATE.mode !== 'FOCUS') { if (STATE.mode !== 'FOCUS') {
STATE.mode = 'FOCUS'; STATE.mode = 'FOCUS';
const photos = particleSystem.filter(p => p.type === 'PHOTO'); const photos = particleSystem.filter(p => p.type === 'PHOTO');
if (photos.length) STATE.focusTarget = photos[Math.floor(Math.random() * photos.length)].mesh; if (photos.length) STATE.focusTarget = photos[Math.floor(Math.random()*photos.length)].mesh;
} }
} else if (avgDist < 0.25) { } else if (avgDist < 0.25) {
STATE.mode = 'TREE'; STATE.mode = 'TREE';
@@ -710,17 +685,27 @@
STATE.mode = 'SCATTER'; STATE.mode = 'SCATTER';
STATE.focusTarget = null; STATE.focusTarget = null;
} }
// 只有在状态发生变化时才进行相关处理
if (prevMode !== STATE.mode) {
console.log(`Mode changed to: ${STATE.mode}`);
}
} else { } else {
STATE.hand.detected = false; STATE.hand.detected = false;
} }
} }
function setupEvents() { function setupEvents() {
// 优化窗口大小调整事件处理,使用防抖动技术
let resizeTimeout;
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
camera.aspect = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight);
}, 100); // 100ms 防抖动延迟
}); });
document.getElementById('file-input').addEventListener('change', handleImageUpload); document.getElementById('file-input').addEventListener('change', handleImageUpload);
@@ -735,18 +720,18 @@
function animate() { function animate() {
requestAnimationFrame(animate); requestAnimationFrame(animate);
const dt = clock.getDelta(); const dt = Math.min(clock.getDelta(), 0.1);
// Rotation Logic // Rotation Logic
if (STATE.mode === 'SCATTER' && STATE.hand.detected) { if (STATE.mode === 'SCATTER' && STATE.hand.detected) {
const targetRotY = STATE.hand.x * Math.PI * 0.9; const targetRotY = STATE.hand.x * Math.PI * 0.9;
const targetRotX = STATE.hand.y * Math.PI * 0.25; const targetRotX = STATE.hand.y * Math.PI * 0.25;
STATE.rotation.y += (targetRotY - STATE.rotation.y) * 3.0 * dt; STATE.rotation.y += (targetRotY - STATE.rotation.y) * Math.min(3.0 * dt, 1.0);
STATE.rotation.x += (targetRotX - STATE.rotation.x) * 3.0 * dt; STATE.rotation.x += (targetRotX - STATE.rotation.x) * Math.min(3.0 * dt, 1.0);
} else { } else {
if (STATE.mode === 'TREE') { if (STATE.mode === 'TREE') {
STATE.rotation.y += 0.3 * dt; STATE.rotation.y += 0.3 * dt;
STATE.rotation.x += (0 - STATE.rotation.x) * 2.0 * dt; STATE.rotation.x += (0 - STATE.rotation.x) * Math.min(2.0 * dt, 1.0);
} else { } else {
STATE.rotation.y += 0.1 * dt; STATE.rotation.y += 0.1 * dt;
} }