创意视觉效果

Canvas 烟花效果

点击夜空即可绽放彩色烟花的互动粒子效果。

HTMLCanvasJavaScript烟花
启用 JavaScript 后可编辑、运行和保存此案例。固定源码如下:
<!doctype html>
<html lang="zh-Hans" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Canvas 烟花效果</title>
  <style>
    * { box-sizing: border-box; }
    body { min-height: 100vh; margin: 0; overflow: hidden; color: #fff; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: radial-gradient(ellipse at bottom, #17234b, #080b1b 60%, #03040a); }
    body::after { content: ""; position: fixed; left: 0; right: 0; bottom: 0; height: 17vh; background: #03050b; clip-path: polygon(0 62%, 7% 62%, 8% 28%, 10% 28%, 11% 62%, 18% 62%, 20% 45%, 24% 45%, 25% 62%, 36% 62%, 38% 18%, 41% 18%, 42% 62%, 55% 62%, 57% 38%, 61% 38%, 62% 62%, 73% 62%, 75% 12%, 78% 12%, 79% 62%, 88% 62%, 90% 44%, 94% 44%, 95% 62%, 100% 62%, 100% 100%, 0 100%); pointer-events: none; }
    canvas { position: fixed; inset: 0; width: 100%; height: 100%; cursor: crosshair; touch-action: none; }
    canvas:focus-visible { outline: 3px solid #f4ce6b; outline-offset: -6px; }
    header { position: fixed; z-index: 2; top: clamp(28px, 7vh, 70px); left: 50%; width: min(680px, calc(100% - 40px)); text-align: center; transform: translateX(-50%); pointer-events: none; }
    .eyebrow { color: #f4ce6b; font-size: 11px; font-weight: 900; letter-spacing: .25em; }
    h1 { margin: 10px 0 12px; font: 500 clamp(43px, 9vw, 84px)/1 Georgia, "STSong", serif; text-shadow: 0 8px 36px rgba(0,0,0,.6); }
    p { margin: 0; color: #aeb8d2; font-size: 14px; letter-spacing: .05em; }
    .hint { position: fixed; z-index: 3; left: 50%; bottom: 28px; padding: 9px 15px; border: 1px solid rgba(255,255,255,.18); border-radius: 999px; color: #c8d0e4; font-size: 11px; background: rgba(5,8,18,.55); transform: translateX(-50%); pointer-events: none; }
  </style>
</head>
<body>
  <canvas id="fireworks" role="button" tabindex="0" aria-label="点击夜空或按回车键绽放烟花"></canvas>
  <header><span class="eyebrow">MAKE A LITTLE CELEBRATION</span><h1>把愿望点亮</h1><p>每一次点击,都会生成一束独一无二的烟花。</p></header>
  <div class="hint">点击或触摸夜空</div>
  <script>
    const canvas = document.querySelector('#fireworks');
    const context = canvas.getContext('2d');
    const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
    let width = 0, height = 0, rockets = [], particles = [], lastAuto = 0;
    const colors = ['#ff5e7d', '#ffd166', '#63e6ff', '#9b8cff', '#73e2a7'];

    function resize() {
      const ratio = Math.min(devicePixelRatio || 1, 2);
      width = innerWidth; height = innerHeight;
      canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio);
      context.setTransform(ratio, 0, 0, ratio, 0, 0);
    }
    function launch(targetX, targetY) {
      rockets.push({ x: width / 2 + (Math.random() - .5) * 100, y: height, targetX, targetY, speed: 7 + Math.random() * 2, hue: colors[Math.floor(Math.random() * colors.length)] });
    }
    function explode(rocket) {
      const count = reduceMotion ? 28 : 70;
      for (let index = 0; index < count; index += 1) {
        const angle = Math.PI * 2 * index / count + Math.random() * .08;
        const speed = 1.5 + Math.random() * 4.8;
        particles.push({ x: rocket.x, y: rocket.y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1, decay: .012 + Math.random() * .012, color: rocket.hue });
      }
    }
    function frame(time) {
      context.fillStyle = 'rgba(3, 4, 10, .2)'; context.fillRect(0, 0, width, height);
      rockets = rockets.filter(rocket => {
        const dx = rocket.targetX - rocket.x, dy = rocket.targetY - rocket.y, distance = Math.hypot(dx, dy);
        if (distance < rocket.speed) { explode(rocket); return false; }
        rocket.x += dx / distance * rocket.speed; rocket.y += dy / distance * rocket.speed;
        context.fillStyle = rocket.hue; context.beginPath(); context.arc(rocket.x, rocket.y, 2.2, 0, Math.PI * 2); context.fill(); return true;
      });
      particles = particles.filter(particle => {
        particle.vy += .035; particle.vx *= .99; particle.x += particle.vx; particle.y += particle.vy; particle.life -= particle.decay;
        if (particle.life <= 0) return false;
        context.globalAlpha = particle.life; context.fillStyle = particle.color; context.beginPath(); context.arc(particle.x, particle.y, 1.8, 0, Math.PI * 2); context.fill(); return true;
      });
      context.globalAlpha = 1;
      if (!reduceMotion && time - lastAuto > 2200) { launch(width * (.2 + Math.random() * .6), height * (.25 + Math.random() * .35)); lastAuto = time; }
      requestAnimationFrame(frame);
    }
    canvas.addEventListener('pointerdown', event => { const rect = canvas.getBoundingClientRect(); launch(event.clientX - rect.left, event.clientY - rect.top); });
    canvas.addEventListener('keydown', event => {
      if (event.key !== 'Enter' && event.key !== ' ') return;
      event.preventDefault();
      launch(width * (.3 + Math.random() * .4), height * (.3 + Math.random() * .25));
    });
    addEventListener('resize', resize); resize(); launch(width * .5, height * .4); requestAnimationFrame(frame);
  </script>
</body>
</html>
页面反馈发现问题或有改进建议?
反馈功能需要在在线版使用

请在在线版启用 JavaScript 后提交;当前工具或案例的本地功能不受影响。

前往在线版反馈