La idea
Por cada clic creamos N partículas. A cada una le damos un ángulo y una distancia al azar, y dejamos que el CSS la anime desde el centro hasta ese punto mientras se desvanece. Al terminar, la borramos del DOM.
const ang = Math.random() * Math.PI * 2;
const d = 40 + Math.random() * 90;
p.style.setProperty('--tx', Math.cos(ang) * d + 'px');
p.style.setProperty('--ty', Math.sin(ang) * d + 'px');
Paso 1 · El CSS de la partícula
La animación va de su punto de origen hasta el desplazamiento --tx/--ty, girando y desvaneciéndose.
.confeti{
position:absolute; width:9px; height:9px; border-radius:2px;
transform:translate(-50%,-50%);
animation:vuela .8s ease-out forwards;
}
@keyframes vuela{
to{
transform:translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) rotate(320deg);
opacity:0;
}
}
Paso 2 · El JavaScript
boton.addEventListener('click', () => {
for (let i = 0; i < 26; i++) {
const p = document.createElement('span');
p.className = 'confeti';
p.style.background = colores[Math.random() * colores.length | 0];
const ang = Math.random() * Math.PI * 2, d = 40 + Math.random() * 90;
p.style.setProperty('--tx', Math.cos(ang) * d + 'px');
p.style.setProperty('--ty', Math.sin(ang) * d + 'px');
escena.appendChild(p);
setTimeout(() => p.remove(), 800);
}
});
Por qué limpiar el DOM
Cada partícula es un <span> real. Si no las borras, con cada clic
se acumulan cientos de nodos invisibles y la página se vuelve lenta. El
setTimeout que las elimina al acabar la animación es clave.
Fiesta lista 🎉
Ya tienes un sistema de partículas básico. La misma idea (crear, animar, destruir) sirve para chispas, humo, estrellas o corazones.