<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超级烟花模拟器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #0a0a0a, #1a1a2e, #0f0f23);
overflow: hidden;
user-select: none;
}
canvas {
display: block;
cursor: crosshair;
background: radial-gradient(ellipse at center, #000428 0%, #004e92 100%);
}
.ui-overlay {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 10px;
}
.ui-panel {
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 15px;
color: white;
min-width: 200px;
}
.control-group {
margin-bottom: 15px;
}
.control-group label {
display: block;
margin-bottom: 5px;
font-size: 12px;
color: #ccc;
text-transform: uppercase;
letter-spacing: 1px;
}
.control-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
input[type="range"] {
flex: 1;
height: 4px;
border-radius: 2px;
background: #333;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
cursor: pointer;
box-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
}
.value-display {
min-width: 30px;
font-size: 11px;
color: #4ecdc4;
text-align: right;
}
button {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 6px;
color: white;
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button.active {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
box-shadow: 0 0 20px rgba(255, 107, 107, 0.6);
}
.firework-type-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5px;
margin-top: 10px;
}
.firework-type-btn {
padding: 6px 8px;
font-size: 10px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.firework-type-btn.active {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
}
.performance-indicator {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 10px;
color: white;
font-size: 11px;
z-index: 1000;
}
.fps-counter {
color: #4ecdc4;
font-weight: bold;
}
.particle-count {
color: #ff6b6b;
}
.text-display {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999;
pointer-events: none;
font-size: 60px;
font-weight: bold;
text-align: center;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.8);
opacity: 0;
transition: all 1s ease;
}
.preset-buttons {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 10px;
}
.preset-btn {
flex: 1;
min-width: 60px;
font-size: 10px;
padding: 5px;
}
.color-palette {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
margin-top: 10px;
}
.color-btn {
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.3s ease;
}
.color-btn.active {
border-color: white;
box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
}
.auto-mode-controls {
display: flex;
gap: 5px;
margin-top: 10px;
}
@keyframes textPulse {
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
}
.text-display.show {
animation: textPulse 3s ease-in-out;
}
</style>
</head>
<body>
<canvas id="fireworksCanvas"></canvas>
<div class="ui-overlay">
<div class="ui-panel">
<h3 style="margin-bottom: 15px; color: #4ecdc4; font-size: 14px;">🎆 烟花控制台</h3>
<div class="control-group">
<label>烟花类型</label>
<div class="firework-type-grid">
<button class="firework-type-btn active" data-type="burst">爆裂</button>
<button class="firework-type-btn" data-type="ring">环形</button>
<button class="firework-type-btn" data-type="heart">心形</button>
<button class="firework-type-btn" data-type="star">星形</button>
<button class="firework-type-btn" data-type="spiral">螺旋</button>
<button class="firework-type-btn" data-type="fountain">喷泉</button>
</div>
</div>
<div class="control-group">
<label>强度设置</label>
<div class="control-row">
<span style="font-size: 10px;">粒子数</span>
<input type="range" id="particleCount" min="20" max="200" value="80">
<span class="value-display" id="particleCountValue">80</span>
</div>
<div class="control-row">
<span style="font-size: 10px;">爆炸力</span>
<input type="range" id="explosionForce" min="1" max="10" value="5" step="0.1">
<span class="value-display" id="explosionForceValue">5.0</span>
</div>
<div class="control-row">
<span style="font-size: 10px;">重力</span>
<input type="range" id="gravity" min="0" max="1" value="0.3" step="0.01">
<span class="value-display" id="gravityValue">0.30</span>
</div>
</div>
<div class="control-group">
<label>视觉效果</label>
<div class="control-row">
<span style="font-size: 10px;">拖尾长度</span>
<input type="range" id="trailLength" min="0.1" max="1" value="0.95" step="0.01">
<span class="value-display" id="trailLengthValue">0.95</span>
</div>
<div class="control-row">
<span style="font-size: 10px;">发光强度</span>
<input type="range" id="glowIntensity" min="0" max="50" value="20">
<span class="value-display" id="glowIntensityValue">20</span>
</div>
</div>
<div class="control-group">
<label>颜色主题</label>
<div class="color-palette">
<div class="color-btn active" style="background: linear-gradient(45deg, #ff6b6b, #feca57)" data-colors="#ff6b6b,#feca57,#48dbfb,#ff9ff3"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #00d2ff, #3a7bd5)" data-colors="#00d2ff,#3a7bd5,#74b9ff,#0984e3"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #a8edea, #fed6e3)" data-colors="#a8edea,#fed6e3,#ffeaa7,#fab1a0"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #ff9a9e, #fecfef)" data-colors="#ff9a9e,#fecfef,#ffecd2,#fcb69f"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #667eea, #764ba2)" data-colors="#667eea,#764ba2,#f093fb,#f5576c"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #4ecdc4, #44a08d)" data-colors="#4ecdc4,#44a08d,#96fbc4,#f9ca24"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #ffeaa7, #fab1a0)" data-colors="#ffeaa7,#fab1a0,#ff7675,#fd79a8"></div>
<div class="color-btn" style="background: linear-gradient(45deg, #a29bfe, #6c5ce7)" data-colors="#a29bfe,#6c5ce7,#fd79a8,#fdcb6e"></div>
</div>
</div>
</div>
<div class="ui-panel">
<h3 style="margin-bottom: 15px; color: #4ecdc4; font-size: 14px;">🎮 播放模式</h3>
<div class="control-group">
<button id="autoMode" style="width: 100%; margin-bottom: 10px;">自动模式</button>
<div class="auto-mode-controls">
<button class="preset-btn" onclick="showText('新年快乐')">新年快乐</button>
<button class="preset-btn" onclick="showText('生日快乐')">生日快乐</button>
<button class="preset-btn" onclick="showText('恭喜发财')">恭喜发财</button>
</div>
<div class="auto-mode-controls">
<button class="preset-btn" onclick="showText('I ❤️ U')">I ❤️ U</button>
<button class="preset-btn" onclick="showText('HAPPY')">HAPPY</button>
<button class="preset-btn" onclick="showText('🎉')">🎉</button>
</div>
</div>
<div class="preset-buttons">
<button class="preset-btn" onclick="setPreset('romantic')">浪漫</button>
<button class="preset-btn" onclick="setPreset('celebration')">庆典</button>
<button class="preset-btn" onclick="setPreset('gentle')">温柔</button>
<button class="preset-btn" onclick="setPreset('intense')">激烈</button>
</div>
<button onclick="clearCanvas()" style="width: 100%; margin-top: 10px; background: linear-gradient(45deg, #ff7675, #d63031);">清空画面</button>
</div>
</div>
<div class="performance-indicator">
<div class="fps-counter">FPS: <span id="fpsCounter">60</span></div>
<div class="particle-count">粒子: <span id="particleCounter">0</span></div>
<div style="color: #ffeaa7;">内存: <span id="memoryUsage">0MB</span></div>
</div>
<div class="text-display" id="textDisplay"></div>
<script>
class FireworkSimulator {
constructor() {
this.canvas = document.getElementById('fireworksCanvas');
this.ctx = this.canvas.getContext('2d');
this.particles = [];
this.trails = [];
this.settings = {
particleCount: 80,
explosionForce: 5,
gravity: 0.3,
trailLength: 0.95,
glowIntensity: 20,
currentType: 'burst',
colors: ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3']
};
this.performance = {
fps: 0,
frameCount: 0,
lastTime: performance.now(),
particlePool: [],
maxParticles: 5000
};
this.autoMode = false;
this.autoInterval = null;
this.init();
this.setupEventListeners();
this.animate();
this.updatePerformanceStats();
}
init() {
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
// 预创建粒子池以优化性能
for (let i = 0; i < this.performance.maxParticles; i++) {
this.performance.particlePool.push(this.createParticle(0, 0, 0, 0, '#ffffff'));
}
}
resizeCanvas() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
setupEventListeners() {
// 鼠标点击事件
this.canvas.addEventListener('click', (e) => {
if (!this.autoMode) {
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
this.createFirework(x, y);
}
});
// 控制面板事件
this.setupControlListeners();
}
setupControlListeners() {
// 范围滑块
['particleCount', 'explosionForce', 'gravity', 'trailLength', 'glowIntensity'].forEach(id => {
const slider = document.getElementById(id);
const valueDisplay = document.getElementById(id + 'Value');
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
this.settings[id] = value;
valueDisplay.textContent = id === 'explosionForce' || id === 'gravity' || id === 'trailLength' ?
value.toFixed(2) : Math.round(value);
});
});
// 烟花类型按钮
document.querySelectorAll('.firework-type-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.firework-type-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
this.settings.currentType = e.target.dataset.type;
});
});
// 颜色主题按钮
document.querySelectorAll('.color-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
this.settings.colors = e.target.dataset.colors.split(',');
});
});
// 自动模式按钮
document.getElementById('autoMode').addEventListener('click', () => {
this.toggleAutoMode();
});
}
createParticle(x, y, vx, vy, color) {
return {
x, y, vx, vy, color,
life: 1,
decay: Math.random() * 0.02 + 0.01,
size: Math.random() * 3 + 1,
alpha: 1,
trail: []
};
}
getParticleFromPool() {
return this.performance.particlePool.pop() || this.createParticle(0, 0, 0, 0, '#ffffff');
}
returnParticleToPool(particle) {
// 重置粒子属性
particle.trail = [];
particle.life = 1;
particle.alpha = 1;
this.performance.particlePool.push(particle);
}
createFirework(x, y) {
const colors = this.settings.colors;
const particleCount = this.settings.particleCount;
const force = this.settings.explosionForce;
// 发射阶段 - 创建上升的火箭
const rocket = this.getParticleFromPool();
Object.assign(rocket, {
x: x,
y: this.canvas.height,
vx: (Math.random() - 0.5) * 2,
vy: -15,
color: colors[Math.floor(Math.random() * colors.length)],
isRocket: true,
targetY: y,
life: 1,
decay: 0.005,
size: 3,
alpha: 1,
trail: []
});
this.particles.push(rocket);
}
explodeFirework(x, y) {
const colors = this.settings.colors;
const particleCount = this.settings.particleCount;
const force = this.settings.explosionForce;
const type = this.settings.currentType;
for (let i = 0; i < particleCount; i++) {
const particle = this.getParticleFromPool();
const color = colors[Math.floor(Math.random() * colors.length)];
let vx, vy;
switch (type) {
case 'burst':
const angle = (Math.PI * 2 * i) / particleCount;
const speed = Math.random() * force + 2;
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
break;
case 'ring':
const ringAngle = (Math.PI * 2 * i) / particleCount;
const ringSpeed = force * 0.8;
vx = Math.cos(ringAngle) * ringSpeed;
vy = Math.sin(ringAngle) * ringSpeed;
break;
case 'heart':
const t = (i / particleCount) * Math.PI * 2;
const heartX = 16 * Math.pow(Math.sin(t), 3);
const heartY = -(13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t));
vx = heartX * force * 0.1;
vy = heartY * force * 0.1;
break;
case 'star':
const starAngle = (Math.PI * 2 * i) / particleCount;
const starSpeed = (i % 2 === 0) ? force * 1.2 : force * 0.6;
vx = Math.cos(starAngle) * starSpeed;
vy = Math.sin(starAngle) * starSpeed;
break;
case 'spiral':
const spiralAngle = (Math.PI * 4 * i) / particleCount;
const spiralRadius = (i / particleCount) * force;
vx = Math.cos(spiralAngle) * spiralRadius;
vy = Math.sin(spiralAngle) * spiralRadius;
break;
case 'fountain':
vx = (Math.random() - 0.5) * force * 0.5;
vy = -Math.random() * force * 1.5;
break;
default:
vx = (Math.random() - 0.5) * force * 2;
vy = (Math.random() - 0.5) * force * 2;
}
Object.assign(particle, {
x, y, vx, vy, color,
life: 1,
decay: Math.random() * 0.02 + 0.01,
size: Math.random() * 3 + 1,
alpha: 1,
trail: [],
isRocket: false
});
this.particles.push(particle);
}
}
updateParticles() {
// 使用倒序循环以安全删除元素
for (let i = this.particles.length - 1; i >= 0; i--) {
const particle = this.particles[i];
if (particle.isRocket) {
// 火箭上升逻辑
particle.x += particle.vx;
particle.y += particle.vy;
particle.vy += 0.2; // 重力影响
// 添加拖尾
particle.trail.push({ x: particle.x, y: particle.y, alpha: particle.alpha });
if (particle.trail.length > 10) {
particle.trail.shift();
}
// 到达目标高度或开始下降时爆炸
if (particle.y <= particle.targetY || particle.vy >= 0) {
this.explodeFirework(particle.x, particle.y);
this.returnParticleToPool(particle);
this.particles.splice(i, 1);
continue;
}
} else {
// 普通粒子逻辑
particle.x += particle.vx;
particle.y += particle.vy;
particle.vy += this.settings.gravity * 0.1;
particle.vx *= 0.99; // 空气阻力
particle.life -= particle.decay;
particle.alpha = particle.life;
// 添加拖尾
particle.trail.push({ x: particle.x, y: particle.y, alpha: particle.alpha });
if (particle.trail.length > 20) {
particle.trail.shift();
}
// 移除死亡粒子
if (particle.life <= 0 || particle.y > this.canvas.height + 50) {
this.returnParticleToPool(particle);
this.particles.splice(i, 1);
}
}
}
}
renderParticles() {
this.ctx.globalCompositeOperation = 'lighter';
for (const particle of this.particles) {
// 绘制拖尾
if (particle.trail.length > 1) {
this.ctx.beginPath();
this.ctx.moveTo(particle.trail[0].x, particle.trail[0].y);
for (let i = 1; i < particle.trail.length; i++) {
const point = particle.trail[i];
const alpha = point.alpha * (i / particle.trail.length) * this.settings.trailLength;
this.ctx.globalAlpha = alpha;
this.ctx.strokeStyle = particle.color;
this.ctx.lineWidth = particle.size * 0.5;
this.ctx.lineTo(point.x, point.y);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.moveTo(point.x, point.y);
}
}
// 绘制粒子本体
this.ctx.globalAlpha = particle.alpha;
// 发光效果
if (this.settings.glowIntensity > 0) {
this.ctx.shadowColor = particle.color;
this.ctx.shadowBlur = this.settings.glowIntensity;
}
this.ctx.fillStyle = particle.color;
this.ctx.beginPath();
this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
this.ctx.fill();
// 重置阴影
this.ctx.shadowBlur = 0;
}
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.globalAlpha = 1;
}
clearCanvas() {
// 使用部分透明度创建拖尾效果
this.ctx.fillStyle = `rgba(0, 4, 40, ${1 - this.settings.trailLength})`;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
toggleAutoMode() {
this.autoMode = !this.autoMode;
const btn = document.getElementById('autoMode');
if (this.autoMode) {
btn.classList.add('active');
btn.textContent = '停止自动';
this.startAutoMode();
} else {
btn.classList.remove('active');
btn.textContent = '自动模式';
this.stopAutoMode();
}
}
startAutoMode() {
this.autoInterval = setInterval(() => {
const x = Math.random() * this.canvas.width;
const y = Math.random() * this.canvas.height * 0.6 + this.canvas.height * 0.1;
this.createFirework(x, y);
}, 1000 + Math.random() * 2000);
}
stopAutoMode() {
if (this.autoInterval) {
clearInterval(this.autoInterval);
this.autoInterval = null;
}
}
updatePerformanceStats() {
setInterval(() => {
const now = performance.now();
this.performance.fps = Math.round(1000 / (now - this.performance.lastTime));
this.performance.lastTime = now;
document.getElementById('fpsCounter').textContent = this.performance.fps;
document.getElementById('particleCounter').textContent = this.particles.length;
// 简单的内存使用估算
const memoryUsage = (this.particles.length * 0.001).toFixed(1);
document.getElementById('memoryUsage').textContent = memoryUsage;
}, 1000);
}
animate() {
this.clearCanvas();
this.updateParticles();
this.renderParticles();
this.performance.frameCount++;
requestAnimationFrame(() => this.animate());
}
}
// 全局函数
function setPreset(type) {
const simulator = window.fireworkSimulator;
switch (type) {
case 'romantic':
simulator.settings.colors = ['#ff9a9e', '#fecfef', '#ffecd2', '#fcb69f'];
simulator.settings.particleCount = 60;
simulator.settings.explosionForce = 3;
simulator.settings.gravity = 0.2;
document.querySelector('[data-type="heart"]').click();
break;
case 'celebration':
simulator.settings.colors = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3'];
simulator.settings.particleCount = 120;
simulator.settings.explosionForce = 7;
simulator.settings.gravity = 0.4;
document.querySelector('[data-type="burst"]').click();
break;
case 'gentle':
simulator.settings.colors = ['#a8edea', '#fed6e3', '#ffeaa7', '#fab1a0'];
simulator.settings.particleCount = 40;
simulator.settings.explosionForce = 2;
simulator.settings.gravity = 0.1;
document.querySelector('[data-type="ring"]').click();
break;
case 'intense':
simulator.settings.colors = ['#667eea', '#764ba2', '#f093fb', '#f5576c'];
simulator.settings.particleCount = 200;
simulator.settings.explosionForce = 10;
simulator.settings.gravity = 0.6;
document.querySelector('[data-type="star"]').click();
break;
}
// 更新UI显示
updateSliderValues();
}
function updateSliderValues() {
const simulator = window.fireworkSimulator;
const settings = simulator.settings;
['particleCount', 'explosionForce', 'gravity', 'trailLength', 'glowIntensity'].forEach(key => {
const slider = document.getElementById(key);
const valueDisplay = document.getElementById(key + 'Value');
if (slider && settings[key] !== undefined) {
slider.value = settings[key];
valueDisplay.textContent = ['explosionForce', 'gravity', 'trailLength'].includes(key) ?
settings[key].toFixed(2) : Math.round(settings[key]);
}
});
}
function showText(text) {
const textDisplay = document.getElementById('textDisplay');
textDisplay.textContent = text;
textDisplay.classList.remove('show');
// 触发重排以重置动画
textDisplay.offsetHeight;
textDisplay.classList.add('show');
// 同时创建特殊烟花效果
const simulator = window.fireworkSimulator;
const canvas = simulator.canvas;
// 创建文字形状的烟花
setTimeout(() => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
const x = canvas.width * 0.2 + Math.random() * canvas.width * 0.6;
const y = canvas.height * 0.2 + Math.random() * canvas.height * 0.4;
simulator.createFirework(x, y);
}, i * 200);
}
}, 500);
}
function clearCanvas() {
const simulator = window.fireworkSimulator;
simulator.particles = [];
simulator.ctx.fillStyle = 'rgba(0, 4, 40, 1)';
simulator.ctx.fillRect(0, 0, simulator.canvas.width, simulator.canvas.height);
}
// 高级特效类
class AdvancedEffects {
constructor(simulator) {
this.simulator = simulator;
this.backgroundParticles = [];
this.musicMode = false;
this.shakeIntensity = 0;
}
// 背景星空效果
createStarField() {
for (let i = 0; i < 100; i++) {
this.backgroundParticles.push({
x: Math.random() * this.simulator.canvas.width,
y: Math.random() * this.simulator.canvas.height,
size: Math.random() * 2,
twinkle: Math.random() * 0.02 + 0.005,
alpha: Math.random()
});
}
}
updateStarField() {
const ctx = this.simulator.ctx;
ctx.globalCompositeOperation = 'lighter';
for (const star of this.backgroundParticles) {
star.alpha += star.twinkle;
if (star.alpha > 1 || star.alpha < 0) {
star.twinkle = -star.twinkle;
}
ctx.globalAlpha = Math.abs(star.alpha) * 0.3;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
}
// 屏幕震动效果
screenShake(intensity = 5) {
this.shakeIntensity = intensity;
const canvas = this.simulator.canvas;
const animate = () => {
if (this.shakeIntensity > 0) {
const shakeX = (Math.random() - 0.5) * this.shakeIntensity;
const shakeY = (Math.random() - 0.5) * this.shakeIntensity;
canvas.style.transform = `translate(${shakeX}px, ${shakeY}px)`;
this.shakeIntensity *= 0.9;
requestAnimationFrame(animate);
} else {
canvas.style.transform = 'translate(0, 0)';
}
};
animate();
}
// 音乐可视化模式
toggleMusicMode() {
this.musicMode = !this.musicMode;
if (this.musicMode) {
this.startMusicVisualization();
}
}
startMusicVisualization() {
// 模拟音频数据创建节拍同步烟花
const beatInterval = setInterval(() => {
if (!this.musicMode) {
clearInterval(beatInterval);
return;
}
const intensity = Math.random() * 0.8 + 0.2;
const count = Math.floor(intensity * 3) + 1;
for (let i = 0; i < count; i++) {
const x = this.simulator.canvas.width * (0.2 + Math.random() * 0.6);
const y = this.simulator.canvas.height * (0.1 + Math.random() * 0.5);
this.simulator.createFirework(x, y);
}
if (intensity > 0.7) {
this.screenShake(8);
}
}, 600 + Math.random() * 400);
}
}
// 粒子系统优化类
class ParticleSystemOptimizer {
constructor(simulator) {
this.simulator = simulator;
this.cullingEnabled = true;
this.lodEnabled = true;
this.maxParticlesPerFrame = 300;
}
// 视锥剔除
cullParticles() {
if (!this.cullingEnabled) return;
const canvas = this.simulator.canvas;
const margin = 50;
this.simulator.particles = this.simulator.particles.filter(particle => {
return particle.x > -margin &&
particle.x < canvas.width + margin &&
particle.y > -margin &&
particle.y < canvas.height + margin;
});
}
// 细节层次(LOD)系统
applyLOD() {
if (!this.lodEnabled) return;
const canvas = this.simulator.canvas;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
for (const particle of this.simulator.particles) {
const distance = Math.sqrt(
Math.pow(particle.x - centerX, 2) +
Math.pow(particle.y - centerY, 2)
);
const maxDistance = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height) / 2;
const lodLevel = distance / maxDistance;
// 根据距离调整粒子细节
if (lodLevel > 0.8) {
particle.size *= 0.5;
particle.trail = particle.trail.slice(-5); // 减少拖尾长度
} else if (lodLevel > 0.6) {
particle.trail = particle.trail.slice(-10);
}
}
}
// 动态质量调整
adjustQuality() {
const fps = this.simulator.performance.fps;
const particleCount = this.simulator.particles.length;
if (fps < 30 && particleCount > 200) {
// 降低质量
this.simulator.settings.trailLength = Math.max(0.8, this.simulator.settings.trailLength - 0.05);
this.simulator.settings.glowIntensity = Math.max(5, this.simulator.settings.glowIntensity - 2);
} else if (fps > 55 && particleCount < 100) {
// 提高质量
this.simulator.settings.trailLength = Math.min(0.98, this.simulator.settings.trailLength + 0.02);
this.simulator.settings.glowIntensity = Math.min(30, this.simulator.settings.glowIntensity + 1);
}
}
}
// 初始化模拟器
document.addEventListener('DOMContentLoaded', () => {
window.fireworkSimulator = new FireworkSimulator();
window.advancedEffects = new AdvancedEffects(window.fireworkSimulator);
window.optimizer = new ParticleSystemOptimizer(window.fireworkSimulator);
// 创建星空背景
window.advancedEffects.createStarField();
// 添加高级特效到动画循环
const originalAnimate = window.fireworkSimulator.animate;
window.fireworkSimulator.animate = function() {
this.clearCanvas();
// 绘制星空背景
window.advancedEffects.updateStarField();
this.updateParticles();
this.renderParticles();
// 应用优化
window.optimizer.cullParticles();
window.optimizer.applyLOD();
window.optimizer.adjustQuality();
this.performance.frameCount++;
requestAnimationFrame(() => this.animate());
};
// 添加键盘快捷键
document.addEventListener('keydown', (e) => {
switch(e.key) {
case ' ':
e.preventDefault();
document.getElementById('autoMode').click();
break;
case 'c':
clearCanvas();
break;
case 'm':
window.advancedEffects.toggleMusicMode();
break;
case '1':
document.querySelector('[data-type="burst"]').click();
break;
case '2':
document.querySelector('[data-type="ring"]').click();
break;
case '3':
document.querySelector('[data-type="heart"]').click();
break;
case '4':
document.querySelector('[data-type="star"]').click();
break;
case '5':
document.querySelector('[data-type="spiral"]').click();
break;
case '6':
document.querySelector('[data-type="fountain"]').click();
break;
}
});
// 添加触摸支持
let touchStartTime;
window.fireworkSimulator.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartTime = Date.now();
});
window.fireworkSimulator.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const touchDuration = Date.now() - touchStartTime;
if (touchDuration < 500) { // 短触摸
const touch = e.changedTouches[0];
const rect = e.target.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
window.fireworkSimulator.createFirework(x, y);
}
});
// 性能监控
setInterval(() => {
const memoryInfo = performance.memory;
if (memoryInfo) {
const memoryUsage = (memoryInfo.usedJSHeapSize / 1024 / 1024).toFixed(1);
document.getElementById('memoryUsage').textContent = memoryUsage + 'MB';
}
}, 2000);
console.log('🎆 Super Fireworks Simulator Loaded!');
console.log('快捷键: 空格键(自动模式), C(清屏), M(音乐模式), 1-6(切换类型)');
});
// 导出配置功能
function exportSettings() {
const settings = window.fireworkSimulator.settings;
const dataStr = JSON.stringify(settings, null, 2);
const dataBlob = new Blob([dataStr], {type:'application/json'});
const link = document.createElement('a');
link.href = URL.createObjectURL(dataBlob);
link.download = 'fireworks-settings.json';
link.click();
}
// 导入配置功能
function importSettings(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const settings = JSON.parse(e.target.result);
Object.assign(window.fireworkSimulator.settings, settings);
updateSliderValues();
console.log('设置已导入成功!');
} catch (error) {
console.error('导入设置失败:', error);
}
};
reader.readAsText(file);
}
// 截图功能
function captureScreenshot() {
const canvas = window.fireworkSimulator.canvas;
const link = document.createElement('a');
link.download = `fireworks-${Date.now()}.png`;
link.href = canvas.toDataURL();
link.click();
}
</script>
</body>
index.html
index.html