cover

Todo mundo hace confetis con canvas, hagamos uno con CSS


Brendi me ha pedido una animación de confetti con estilo “brutalism” para que combine bien con su diseño, así que, rápido, me puse a codear una versión bonita con canvas, pues me encanta trabajar con la etiqueta <canvas> de HTML. 🤓

Sin embargo, a Brendi no le ha gustado mi versión con canvas, porque mi estrategia es una pincelada y no un rectángulo. 🫠

Entonces, pensé que podría intentar crear confeti solo con etiquetas <div> y un poquito de CSS, y de paso podría practicar un poco más con la Animation_API de la plataforma. 🔥👨🏻‍🚒🧯🔥

Así que, me emocioné lo suficiente y me puse a trabajar (a leer código de otros, obvio) y escribí un componente que llena tu pantalla con 80 papelitos que caen en un vaivén gracioso y girando elegantemente. 🍃

Te dejo el link para que lo veas aquí.

En esta entrada te voy a mostrar las piezas de este componente. Si no quieres construirlo y prefieres solo usarlo, copialo de aquí.

Pero, si sientes curiosidad, pues vente conmigo…

¡Construyamos nuestra propia animación de confetti sin canvas! 🎊

Vamos a ponerlo todo en un componente

Como queremos reutilizar nuestro confetti por aquí y por allá, pues vamos a poner nuestro código en un componente de React, lo que nos facilitará poder emplearlo en cualquiera de nuestras rutas (estamos usando React Router para este proyecto).

Creamos pues nuestro archivo y nuestra función.

export const BrendisConfetti = ({ duration }: { duration?: number }) => { const len = 50; useEffect(() => { document.querySelectorAll(".confetti").forEach((element, i) => { const scale = Math.random() * 0.6 + 0.5; // puedes jugar con esto element.animate( { opacity: [scale], transform: [ `translate3d(${ (i / len) * 100 // x }vw,-5vh,0) scale(${scale}) rotate(0turn)`, `translate3d(${ (i / len) * 100 + 10 // x }vw, 105vh,0) scale(${scale}) rotate(${ Math.random() > 0.5 ? "" : "-" // izq o der }2turn)`, ], }, { duration: Math.random() * 3000 + 4000, // 4-7 iterations: Infinity, delay: (Math.random() * 7000), } ); }); }, []); return Array.from({ length: len/2 }).map((_, i) => ( <section key={i}> <div className="confetti"> <div className={cn("rotate", { yellow: i % 2 === 0, purple: i % 2 !== 0, })} /> </div> <div className="confetti"> <div className={cn("askew", { yellow: i % 2 === 0, purple: i % 2 !== 0, })} /> </div> </section> )); };

Observa en el JSX que colocamos un contenedor con dos elementos .confetti, este elemento contiene dos hijos con las clases: rotate y askew. Estos nombres describen su comportamiento (su animación que estamos por crear) y añadimos también la clase del color en caso de que el index del elemento sea par o impar. ✅

Bueno Lo que sigue lo vamos a dividir en tres partes:

  1. Vamos a crear las clases CSS necesarias en un archivo aparte para poderlas importar fácilmente en el archivo del componente (esto, gracias a Vite)
  2. Luego, ya con los estilos en su lugar, añadiremos dos animaciones más con @keyframes
  3. Finalmente, añadiremos algunos delays a diferentes elementos para crear más singularidad

Eso, no mucho más, nos ayudaremos con VanillaJS y algunos de mis número mágicos que colecciono. Bueno, basta de especular ¡es hora de crear nuestro archivo .css!

Creando el archivo de estilos

He llamado a mi archivo brendisConfetti.css y he colocado todo lo que necesitamos de una vez, obsérvalo un poco, que ya te lo explico.

/* Animación de confetti experimento */ .confetti { width: 1rem; height: 1rem; display: inline-block; position: absolute; top: 0rem; left: 0rem; z-index: 50; transform-style: preserve-3d; user-select: none; } .yellow { background: hsl(50deg 77% 68%); } .purple { background: #9870ed; } .confetti .rotate { animation: driftyRotate 1s infinite both ease-in-out; perspective: 1000; height: 2rem; border: 2px solid black; // <= brutalism } .confetti .askew { perspective: 600; transform: skewY(10deg); width: 1rem; height: 2rem; animation: drifty 1s infinite alternate both ease-in-out; border: 2px solid black; clip-path: polygon(evenodd, 0% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 0%); } @keyframes drifty { 0% { transform: skewY(10deg) translate3d(-250%, 0, 0); } 100% { transform: skewY(-12deg) translate3d(250%, 0, 0); } } @keyframes driftyRotate { 0% { transform: rotateX(0); } 100% { transform: rotateX(359deg); } }

Podemos leer este archivo CSS en dos partes. Primero están los tamaños y colores de los dos tipos de confetti que tendremos, luego encontraremos las dos animaciones hechas con keyframes para hacer el movimiento de vaivén de nuestro papelito y su rotación.

Necesitamos que cada uno de los papelitos tenga su propio delay y su propia duración para sus animaciones, así que, con una utilidad llamada rand, vamos a generar diferentes tiempos.

const rand = (min = 0, max = (min = 0)) => Math.random() * (max - min) + min; return Array.from({ length: len }).map((_, i) => ( <section key={i}> <div className="confetti"> <div style={{ animationDuration: `${rand(0.6, 2.8)}s`, // experimenta con los tiempos }} className={cn("rotate")} > <div style={{ animationDuration: `${rand(1, 2)}s`, animationDelay: `${rand(1, 3)}s`, }} className={cn("askew", { yellow: i % 2 === 0, purple: i % 2 !== 0, })} /> </div> </div> </section> ));

A rand() no la veas mucho, es solo una de muchas formas de generar un número random, leela con calma. 📖

Realmente eso es todo, tal vez, solo nos faltaría importar los estilos en el componente para que vayan con él donde quiera que se use:

import "~/styles/brendisConfetti.css"; // drifty animations export const BrendisConfetti = () => { const len = 80; // ...

Esto podemos hacerlo gracias a Vite, que es lo que usamos como bundler. Y ya está, te dejo enlaces a todo el código y a mi mayor referencia. Espero lo hayas disfrutado.

Te dejo una versión funcionando en codePen que no usa React 😉

Abrazo. Bliss. 🤓

Enlaces relacionados

El codepen

El código del componente

Mi mayor referencia

meta cover

Cómo Publicar tu proyecto Vite en Github Pages con el método más fácil

Checa este otro Post

meta cover

Motion One es la única biblioteca de animaciones JS que querrás usar.

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻