/forum/4

这个脚本可以在任意一个页面放烟花

W WillZhong RichMan 51 views
(() => {
  // 防止重复注入
  if (window.__fireworksCleanup__) {
    window.__fireworksCleanup__();
  }

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.style.cssText = `
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    z-index: 2147483647;
  `;
  document.documentElement.appendChild(canvas);

  let w = canvas.width = window.innerWidth * devicePixelRatio;
  let h = canvas.height = window.innerHeight * devicePixelRatio;
  ctx.scale(devicePixelRatio, devicePixelRatio);

  const fireworks = [];
  const particles = [];
  let running = true;
  let rafId = null;
  let autoLaunchTimer = null;

  function rand(min, max) {
    return Math.random() * (max - min) + min;
  }

  function hsl(h, s, l, a = 1) {
    return `hsla(${h}, ${s}%, ${l}%, ${a})`;
  }

  class Firework {
    constructor(sx, sy, tx, ty) {
      this.x = sx;
      this.y = sy;
      this.sx = sx;
      this.sy = sy;
      this.tx = tx;
      this.ty = ty;
      this.distanceToTarget = Math.hypot(tx - sx, ty - sy);
      this.distanceTraveled = 0;
      this.angle = Math.atan2(ty - sy, tx - sx);
      this.speed = rand(4, 7);
      this.acceleration = 1.03;
      this.brightness = rand(50, 70);
      this.hue = rand(0, 360);
      this.trail = [];
      this.trailLength = 5;
    }

    update(i) {
      this.trail.push([this.x, this.y]);
      if (this.trail.length > this.trailLength) this.trail.shift();

      this.speed *= this.acceleration;
      const vx = Math.cos(this.angle) * this.speed;
      const vy = Math.sin(this.angle) * this.speed;

      this.distanceTraveled = Math.hypot(this.x + vx - this.sx, this.y + vy - this.sy);

      if (this.distanceTraveled >= this.distanceToTarget) {
        explode(this.tx, this.ty, this.hue);
        fireworks.splice(i, 1);
      } else {
        this.x += vx;
        this.y += vy;
      }
    }

    draw() {
      ctx.beginPath();
      const last = this.trail[0] || [this.x, this.y];
      ctx.moveTo(last[0], last[1]);
      ctx.lineTo(this.x, this.y);
      ctx.strokeStyle = hsl(this.hue, 100, this.brightness);
      ctx.lineWidth = 2;
      ctx.stroke();
    }
  }

  class Particle {
    constructor(x, y, hue) {
      this.x = x;
      this.y = y;
      this.hue = rand(hue - 20, hue + 20);
      this.angle = rand(0, Math.PI * 2);
      this.speed = rand(1, 10);
      this.friction = 0.96;
      this.gravity = 0.06;
      this.alpha = 1;
      this.decay = rand(0.008, 0.02);
      this.brightness = rand(50, 80);
      this.trail = [];
      this.trailLength = 8;
    }

    update(i) {
      this.trail.push([this.x, this.y]);
      if (this.trail.length > this.trailLength) this.trail.shift();

      this.speed *= this.friction;
      this.x += Math.cos(this.angle) * this.speed;
      this.y += Math.sin(this.angle) * this.speed + this.gravity;
      this.alpha -= this.decay;

      if (this.alpha <= 0) particles.splice(i, 1);
    }

    draw() {
      ctx.beginPath();
      const last = this.trail[0] || [this.x, this.y];
      ctx.moveTo(last[0], last[1]);
      ctx.lineTo(this.x, this.y);
      ctx.strokeStyle = hsl(this.hue, 100, this.brightness, this.alpha);
      ctx.lineWidth = 1.5;
      ctx.stroke();
    }
  }

  function explode(x, y, hue) {
    const count = Math.floor(rand(40, 90));
    for (let i = 0; i < count; i++) {
      particles.push(new Particle(x, y, hue));
    }
  }

  function launch(x = rand(w * 0.1 / devicePixelRatio, w * 0.9 / devicePixelRatio), y = rand(50, h * 0.5 / devicePixelRatio)) {
    const sx = rand(0.2, 0.8) * (w / devicePixelRatio);
    const sy = h / devicePixelRatio;
    fireworks.push(new Firework(sx, sy, x, y));
  }

  function loop() {
    if (!running) return;
    ctx.globalCompositeOperation = 'destination-out';
    ctx.fillStyle = 'rgba(0,0,0,0.2)';
    ctx.fillRect(0, 0, w / devicePixelRatio, h / devicePixelRatio);
    ctx.globalCompositeOperation = 'lighter';

    for (let i = fireworks.length - 1; i >= 0; i--) {
      fireworks[i].draw();
      fireworks[i].update(i);
    }

    for (let i = particles.length - 1; i >= 0; i--) {
      particles[i].draw();
      particles[i].update(i);
    }

    rafId = requestAnimationFrame(loop);
  }

  function onResize() {
    w = canvas.width = window.innerWidth * devicePixelRatio;
    h = canvas.height = window.innerHeight * devicePixelRatio;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(devicePixelRatio, devicePixelRatio);
  }

  function onClick(e) {
    // 点击位置放烟花
    launch(e.clientX, e.clientY);
  }

  function onKeydown(e) {
    if (e.key === 'Escape') {
      cleanup();
    }
  }

  function cleanup() {
    running = false;
    cancelAnimationFrame(rafId);
    clearInterval(autoLaunchTimer);
    window.removeEventListener('resize', onResize);
    window.removeEventListener('click', onClick);
    window.removeEventListener('keydown', onKeydown);
    canvas.remove();
    delete window.__fireworksCleanup__;
    console.log('🎆 Fireworks stopped and removed.');
  }

  window.__fireworksCleanup__ = cleanup;

  window.addEventListener('resize', onResize);
  window.addEventListener('click', onClick);
  window.addEventListener('keydown', onKeydown);

  // 自动发射
  autoLaunchTimer = setInterval(() => {
    launch();
    if (Math.random() < 0.35) setTimeout(() => launch(), rand(80, 250));
    if (Math.random() < 0.2) setTimeout(() => launch(), rand(120, 400));
  }, 500);

  // 初始几发
  for (let i = 0; i < 3; i++) setTimeout(() => launch(), i * 180);

  loop();
  console.log('🎆 Fireworks running! Click anywhere to launch. Press Esc to stop.');
})();

在开发者界面console复制上面的代码运行即可

Replies

Live
0 replies

No replies yet

还没有回复,来说两句吧。

登录 后参与讨论