Explosion de confettis au clic. Particules avec physique (gravite, velocite, rotation).
"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>;
}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.