Animations/Confetti

Confetti

Explosion de confettis au clic. Particules avec physique (gravite, velocite, rotation).

clickJSparticlescelebrationinteractive
Trigger: Click
CSS requis: Non
Difficulte: medium

Preview live

Code

"use client";
import { useRef, useCallback } from "react";

export default function Confetti() {
  const containerRef = useRef<HTMLDivElement>(null);

  const fire = useCallback((e: React.MouseEvent) => {
    const container = containerRef.current;
    if (!container) return;
    const rect = container.getBoundingClientRect();
    const originX = e.clientX - rect.left;
    const originY = e.clientY - rect.top;

    for (let i = 0; i < 50; i++) {
      const el = document.createElement("div");
      const size = Math.random() * 8 + 4;
      const color = ["#E1FF6C", "#6CE1FF", "#FF6CE1", "#FF6C6C", "#6CFF9E"][Math.floor(Math.random() * 5)];
      el.style.cssText = `position:absolute;width:${size}px;height:${size}px;background:${color};border-radius:${Math.random()>0.5?"50%":"2px"};left:${originX}px;top:${originY}px;pointer-events:none`;
      container.appendChild(el);

      let vx = (Math.random() - 0.5) * 12;
      let vy = -Math.random() * 15 - 5;
      let frame = 0;
      const animate = () => {
        vy += 0.5; // gravity
        el.style.left = `${parseFloat(el.style.left) + vx}px`;
        el.style.top = `${parseFloat(el.style.top) + vy}px`;
        el.style.opacity = String(1 - frame / 60);
        if (++frame < 60) requestAnimationFrame(animate);
        else el.remove();
      };
      requestAnimationFrame(animate);
    }
  }, []);

  return <div ref={containerRef} onClick={fire} style={{ position: "relative", overflow: "hidden" }}><button>Celebrate!</button></div>;
}

Instructions

Confetti au clic avec physique (gravite 0.5, velocite aleatoire). 50 particules avec formes mixtes (rond/carre) et 5 couleurs. Les particules s'auto-suppriment apres 60 frames. Le container doit etre position: relative.