efectos visuales creativos

Efecto de fuegos artificiales en Canvas

Haz clic en el cielo nocturno para desatar un efecto interactivo de partículas con coloridos fuegos artificiales.

HTMLCanvasJavaScriptFuegos artificiales
Tras activar JavaScript, puedes editar, ejecutar y guardar este ejemplo. El código fuente fijado es el siguiente:
<!doctype html>
<html lang="es" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Efecto de fuegos artificiales en 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="Haz clic en el cielo nocturno o pulsa Intro para lanzar fuegos artificiales"></canvas>
  <header><span class="eyebrow">MAKE A LITTLE CELEBRATION</span><h1>Enciende un deseo</h1><p>Cada clic genera un haz de fuegos artificiales único.</p></header>
  <div class="hint">Toca o haz clic en el cielo nocturno</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>
Comentarios de la página¿Has encontrado un problema o tienes sugerencias de mejora?
Las funciones de retroalimentación deben usarse en la versión en línea

Envía tras activar JavaScript en la versión en línea; las funciones locales de la herramienta o del ejemplo no se verán afectadas.

Ir a la versión en línea para dar feedback