Effets visuels créatifs

Canvas effet de feu d’artifice

Effet de particules interactif qui fait éclore des feux d’artifice colorés dans le ciel nocturne au clic.

HTMLCanvasJavaScriptFeux d’artifice
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Canvas effet de feu d’artifice</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="Cliquez sur le ciel nocturne ou appuyez sur Entrée pour faire éclore des feux d’artifice"></canvas>
  <header><span class="eyebrow">MAKE A LITTLE CELEBRATION</span><h1>Allumer les vœux</h1><p>Chaque clic génère un feu d’artifice unique.</p></header>
  <div class="hint">Cliquez ou touchez le ciel nocturne</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>
Commentaires sur la pageVous avez découvert un problème ou avez une suggestion d’amélioration ?
Les fonctions de retour nécessitent l’utilisation de la version en ligne

Veuillez activer JavaScript dans la version en ligne avant de soumettre ; les fonctionnalités locales de l’outil ou du cas ne sont pas affectées.

Aller vers la version en ligne pour envoyer des commentaires