Untitled
Anonymous
plain_text
02/17/2026 8:14 AM
8.3 KB
9
Indexable
<!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{background:#000;overflow:hidden;}
canvas{display:block;width:100vw;height:100vh;}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
function initAudio() {
if (!audioCtx) audioCtx = new AudioContext();
}
function playLaunchSound() {
initAudio();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(700, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.5);
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
function playExplosionSound() {
initAudio();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(20, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.3);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.3);
}
const colors = [
'#ff4444','#ff6633','#ff9922','#ffcc22','#ffee22',
'#aaff22','#33ff66','#22ffaa','#22ffee','#33ccff',
'#3399ff','#5566ff','#8844ff','#aa33ff','#ff33cc','#ffffff'
];
class Particle {
constructor(x, y, angle, speed) {
this.x = x;
this.y = y;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = Math.random() * 70 + 50;
this.maxLife = this.life;
this.size = Math.random() * 3 + 1.5;
this.color = colors[Math.floor(Math.random() * colors.length)];
}
update(){
this.life--;
this.x += this.vx;
this.y += this.vy;
this.vy += 0.04;
this.vx *= 0.97;
this.vy *= 0.97;
}
draw(){
ctx.globalAlpha = this.life / this.maxLife;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
class Firework {
constructor() {
this.startX = Math.random() * canvas.width;
this.startY = canvas.height;
this.targetX = Math.random() * canvas.width;
this.targetY = Math.random() * canvas.height * 0.5 + 80;
this.x = this.startX;
this.y = this.startY;
this.speed = Math.random() * 2 + 5;
this.state = 'launching';
this.particles = [];
playLaunchSound();
}
update() {
if (this.state === 'launching') {
const dx = this.targetX - this.x;
const dy = this.targetY - this.y;
const dist = Math.hypot(dx, dy);
if (dist < this.speed) {
this.explode();
} else {
this.x += dx / dist * this.speed;
this.y += dy / dist * this.speed;
}
} else {
this.particles = this.particles.filter(p => p.life > 0);
this.particles.forEach(p => p.update());
}
}
draw() {
if (this.state === 'launching') {
ctx.globalAlpha = 0.9;
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
} else {
this.particles.forEach(p => p.draw());
}
}
explode() {
this.state = 'exploded';
playExplosionSound();
const count = 320;
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
const speed = Math.random() * 4 + 3.5;
this.particles.push(new Particle(this.x, this.y, angle, speed));
}
}
isDead() {
return this.state === 'exploded' && this.particles.length === 0;
}
}
const fireworks = [];
// 一打开就立刻炸两朵!
function immediateStartFireworks() {
for (let i = 0; i < 2; i++) {
const fw = new Firework();
fw.x = Math.random() * canvas.width;
fw.y = Math.random() * canvas.height * 0.4 + 100;
fw.explode();
fireworks.push(fw);
}
}
function autoStart() {
setInterval(() => {
fireworks.push(new Firework());
}, 320);
}
// ========== 春晚高級版:繁體 + 炫彩漸變文字 ==========
function drawText() {
const cx = canvas.width / 2;
const cy = canvas.height / 2 - 120;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '500 70px "Microsoft YaHei", sans-serif';
ctx.fillStyle = 'rgba(255, 180, 100, 0.2)';
ctx.fillText('馬年大吉', cx + 1, cy + 1);
const g = ctx.createLinearGradient(0, cy - 35, 0, cy + 15);
g.addColorStop(0, '#ff4a4a');
g.addColorStop(0.3, '#ff9d4d');
g.addColorStop(0.6, '#ffe066');
g.addColorStop(1, '#ffffff');
ctx.fillStyle = g;
ctx.fillText('馬年大吉', cx, cy);
ctx.font = '300 32px "Microsoft YaHei", sans-serif';
ctx.fillStyle = 'rgba(255,255,255,0.75)';
ctx.fillText('2026 · 一馬當先', cx, cy + 70);
}
function loop() {
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const r = Math.max(canvas.width, canvas.height) / 1.3;
const grd = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
grd.addColorStop(0, 'rgba(0,0,0,0.1)');
grd.addColorStop(1, 'rgba(0,0,0,0.95)');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((fw, i) => {
fw.update();
fw.draw();
if (fw.isDead()) fireworks.splice(i, 1);
});
drawText();
requestAnimationFrame(loop);
}
// 先炸两朵 → 再正常发射
immediateStartFireworks();
autoStart();
loop();
canvas.addEventListener('click', () => fireworks.push(new Firework()));
</script>
</body>
</html>Editor is loading...
Leave a Comment