Skip to Content

Welcome .


Sign up

Cette question a été signalée
1 Reply
74 Views

Voilà une animation en html/js de Feux d'artifices

<!-- === FEUX D'ARTIFICE — SNIPPET AUTONOME === -->
<div
  id="fireworks-odoo-1"
  class="fw-container"
  data-autoplay="true"                  <!-- "false" pour déclencher seulement au clic -->
  data-density="1.0"                    <!-- 0.3 (léger) → 1.5 (chargé) -->
  data-max="450"                        <!-- nombre max de particules vivantes -->
  data-colors="#ff3b3b,#ffd93b,#3bff9c,#3bbdff,#b93bff"  <!-- palette -->
  data-height="420"                     <!-- hauteur en px (optionnel) -->
  style="position:relative; width:100%; max-width:100%; display:block;"
></div>

<style>
  /* Le canvas couvre le conteneur */
  .fw-container canvas {
    position:absolute; inset:0; width:100%; height:100%;
    display:block; pointer-events:auto; touch-action:manipulation;
  }
  /* Hauteur par défaut si data-height non fourni */
  .fw-container { min-height: 360px; }
  @media (prefers-reduced-motion: reduce) {
    .fw-container { background: radial-gradient(ellipse at bottom, #0b1020, #04070f); }
  }
</style>

<script>
(function () {
  "use strict";

  // ====== UTIL ======
  const rand = (min, max) => min + Math.random() * (max - min);
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  const parseColors = (str) =>
    (str || "").split(",").map(s => s.trim()).filter(Boolean);

  // ====== CLASSES ======
  class Particle {
    constructor(x, y, vx, vy, color, life, size) {
      this.x = x; this.y = y;
      this.vx = vx; this.vy = vy;
      this.ax = 0; this.ay = 0.06; // gravité
      this.drag = 0.995;
      this.life = life; this.maxLife = life;
      this.size = size;
      this.color = color;
      this.dead = false;
      this.spark = Math.random() < 0.15;
    }
    step() {
      this.vx *= this.drag; this.vy = this.vy * this.drag + this.ay;
      this.x += this.vx; this.y += this.vy;
      this.life--;
      if (this.life <= 0) this.dead = true;
    }
    draw(ctx) {
      const t = this.life / this.maxLife; // 1 → 0
      const alpha = Math.pow(t, 1.5);
      ctx.globalCompositeOperation = "lighter";
      ctx.globalAlpha = alpha;
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
      ctx.fillStyle = this.color;
      ctx.fill();
      if (this.spark && Math.random() < 0.3) {
        ctx.globalAlpha = alpha * 0.7;
        ctx.fillRect(this.x, this.y, 1, 1);
      }
      ctx.globalAlpha = 1;
    }
  }

  class Rocket {
    constructor(x, y, targetY, hueColor) {
      this.x = x; this.y = y;
      this.vx = rand(-0.8, 0.8);
      this.vy = rand(-9.5, -7.8);
      this.targetY = targetY;
      this.dead = false;
      this.hueColor = hueColor;
      this.trail = [];
      this.maxTrail = 6;
    }
    step() {
      this.vy += 0.06; // gravité
      this.x += this.vx; this.y += this.vy;
      this.trail.push({x:this.x, y:this.y});
      if (this.trail.length > this.maxTrail) this.trail.shift();
      if (this.vy >= 0 || this.y <= this.targetY) this.dead = true; // apogée
    }
    draw(ctx) {
      ctx.globalCompositeOperation = "lighter";
      ctx.strokeStyle = this.hueColor;
      ctx.lineWidth = 2;
      ctx.beginPath();
      for (let i=0;i<this.trail.length;i++) {
        const p = this.trail[i];
        if (i === 0) ctx.moveTo(p.x, p.y); else ctx.lineTo(p.x, p.y);
      }
      ctx.stroke();
      ctx.beginPath();
      ctx.arc(this.x, this.y, 2, 0, Math.PI*2);
      ctx.fillStyle = this.hueColor; ctx.fill();
    }
  }

  // ====== MOTEUR ======
  function createFireworks(container) {
    const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d", { alpha: true });
    container.appendChild(canvas);

    // Options via data-*
    const autoplay = (container.dataset.autoplay || "true") === "true";
    const density = clamp(parseFloat(container.dataset.density || "1.0"), 0.1, 2.0);
    const maxParticles = Math.floor(clamp(parseInt(container.dataset.max || "450", 10), 100, 1200));
    const palette = parseColors(container.dataset.colors) ;
    const userHeight = parseInt(container.dataset.height || "0", 10);

    // Taille
    function resize() {
      const rect = container.getBoundingClientRect();
      const w = Math.floor(rect.width);
      const h = Math.floor(userHeight > 0 ? userHeight : rect.height || 360);
      canvas.width = Math.max(1, w * dpr);
      canvas.height = Math.max(1, h * dpr);
      canvas.style.width = w + "px";
      canvas.style.height = h + "px";
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();
    let ro;
    if ("ResizeObserver" in window) {
      ro = new ResizeObserver(resize);
      ro.observe(container);
    } else {
      window.addEventListener("resize", resize);
    }

    // État
    const particles = [];
    const rockets = [];
    let running = true;
    let lastTime = performance.now();
    let accum = 0;
    const spawnInterval = 800 / density; // ms entre rockets auto
    const prefersReduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    // Utilitaires
    const pickColor = () => {
      if (palette.length) return palette[Math.floor(Math.random() * palette.length)];
      // fallback: couleurs HSL variées
      const h = Math.floor(rand(0, 360));
      return `hsl(${h} 100% 60%)`;
    };

    function explode(x, y) {
      const color = pickColor();
      const count = Math.floor(rand(60, 120) * density);
      const speed = rand(2.5, 4.2);
      for (let i = 0; i < count; i++) {
        if (particles.length >= maxParticles) break;
        const angle = (i / count) * Math.PI * 2 + rand(-0.04, 0.04);
        const mag = speed * (0.6 + Math.random() * 0.8);
        const vx = Math.cos(angle) * mag;
        const vy = Math.sin(angle) * mag;
        const life = Math.floor(rand(45, 90));
        const size = rand(1.2, 2.4);
        particles.push(new Particle(x, y, vx, vy, color, life, size));
      }
    }

    function launchRandom() {
      const w = canvas.width / dpr;
      const h = canvas.height / dpr;
      const x = rand(w * 0.1, w * 0.9);
      const y = h + 10;
      const targetY = rand(h * 0.18, h * 0.45);
      const rocket = new Rocket(x, y, targetY, pickColor());
      rockets.push(rocket);
    }

    // Interaction
    function pointer(e) {
      const rect = canvas.getBoundingClientRect();
      const x = (e.clientX ?? (e.touches && e.touches[0].clientX)) - rect.left;
      const y = (e.clientY ?? (e.touches && e.touches[0].clientY)) - rect.top;
      explode(x, y);
    }
    canvas.addEventListener("click", pointer, { passive: true });
    canvas.addEventListener("touchend", (e) => { if (e.changedTouches && e.changedTouches[0]) {
      const t = e.changedTouches[0];
      const rect = canvas.getBoundingClientRect();
      explode(t.clientX - rect.left, t.clientY - rect.top);
    }}, { passive: true });

    // API simple pour Odoo (start/stop)
    const api = {
      start() { running = true; },
      stop() { running = false; },
      destroy() {
        running = false;
        canvas.remove();
        ro && ro.disconnect();
        window.removeEventListener("resize", resize);
      }
    };
    container._fireworks = api;
    if (!window.fireworks) window.fireworks = {};
    window.fireworks[container.id] = api;

    // Boucle
    let spawnTimer = 0;
    function frame(now) {
      const dt = now - lastTime; lastTime = now;
      if (!running) { requestAnimationFrame(frame); return; }

      // Effet "traînée"
      ctx.globalCompositeOperation = "source-over";
      ctx.fillStyle = "rgba(0,0,16,0.25)";
      ctx.fillRect(0, 0, canvas.width / dpr, canvas.height / dpr);

      // Auto-spawn
      if (autoplay && !prefersReduce) {
        spawnTimer += dt;
        if (spawnTimer >= spawnInterval) {
          spawnTimer = 0;
          if (particles.length < maxParticles * 0.7) launchRandom();
        }
      }

      // Update/draw rockets
      for (let i = rockets.length - 1; i >= 0; i--) {
        const r = rockets[i];
        r.step(); r.draw(ctx);
        if (r.dead) {
          explode(r.x, r.y);
          rockets.splice(i, 1);
        }
      }

      // Update/draw particles
      for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.step(); p.draw(ctx);
        // purge hors écran ou morts
        if (p.dead || p.y > canvas.height / dpr + 50) {
          particles.splice(i, 1);
        }
      }

      requestAnimationFrame(frame);
    }
    requestAnimationFrame((t) => { lastTime = t; frame(t); });

    // Pause quand l'onglet n'est pas visible
    document.addEventListener("visibilitychange", () => {
      running = document.visibilityState === "visible";
    });
  }

  // Init auto pour ce conteneur
  document.addEventListener("DOMContentLoaded", () => {
    const el = document.getElementById("fireworks-odoo-1");
    if (el) createFireworks(el);
  });
})();
</script>
<!-- === FIN SNIPPET === -->

Ignorer
Auteur

Vous pouvez démarrer/arreter le code via la console :

// arrêter
window.fireworks["fireworks-odoo-1"].stop();
// redémarrer
window.fireworks["fireworks-odoo-1"].start();

Ignorer
Related Posts Replies Views Activity
0
Aug 25
39
0
Aug 25
1
0
Aug 25
42
0
Aug 25
29
0
Aug 25
3