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