5 puntos por GN⁺ 2024-04-26 | 1 comentarios | Compartir por WhatsApp
  • canvas-confetti es una biblioteca cliente para ejecutar animaciones de confetti basadas en canvas en páginas web, compatible tanto con instalación vía NPM como con inclusión directa mediante CDN
  • La API básica confetti() permite ajustar, con un único objeto de opciones, la cantidad de partículas, el ángulo, la dispersión, la velocidad, la gravedad, los colores, las formas, la posición, el z-index y más; en entornos con soporte para Promise, permite recibir el momento en que termina la animación
  • Para usuarios con Reduced Motion, ofrece la opción disableForReducedMotion; actualmente su valor predeterminado es false, pero podría cambiar en una futura versión mayor
  • Permite crear formas personalizadas basadas en SVG Path y texto, y además de las formas predeterminadas square, circle y star, también se pueden implementar efectos como emoji confetti
  • confetti.create() crea una instancia sobre un canvas específico y admite opciones globales como resize y useWorker, pero con useWorker: true el control del canvas se transfiere a un web worker, por lo que manipularlo desde el hilo principal provoca errores

Instalación y formas de ejecución

  • Puedes ver el funcionamiento de la biblioteca en la página de demo
  • Se puede instalar como paquete de NPM
npm install --save canvas-confetti
  • En builds de proyecto se puede usar con require('canvas-confetti')
  • Esta biblioteca es un componente cliente y no se ejecuta en Node
    • El README indica que el proyecto debe compilarse con herramientas como webpack
  • En una página HTML se puede incluir directamente con un script desde CDN
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/…;
  • Al usar CDN, se recomienda usar la versión más reciente disponible al momento de incluirla en el proyecto; la lista completa de versiones puede consultarse en la página de releases

Soporte para Reduced Motion

  • Algunos usuarios pueden no querer movimiento en los sitios web o preferir reducirlo, y el navegador puede comunicarlo mediante prefers-reduced-motion
  • Con la opción disableForReducedMotion, se puede evitar mostrar confetti a usuarios para quienes las animaciones confusas resultan difíciles
  • El valor predeterminado actual de esta opción es false
  • Se está considerando cambiar el valor predeterminado en una futura versión mayor; si tienes una opinión firme, puedes comunicarla mediante un issue
  • Si disableForReducedMotion se aplica y el confetti queda deshabilitado, la Promise de confetti() se resuelve de inmediato

API básica y comportamiento de Promise

  • Al instalarlo con NPM, se puede requerir como componente cliente en el build del proyecto, y en la versión CDN se expone como la función confetti en window
  • confetti([options]) recibe un único objeto de opciones opcional
  • Si existe window.Promise, devuelve una Promise que notifica la finalización de la animación
    • En entornos sin Promise, como IE, devuelve null
    • Se puede usar un polyfill de Promise
    • También se puede proporcionar directamente una implementación de Promise con la forma confetti.Promise = MyPromise
  • Si se llama varias veces a confetti antes de que termine, siempre devuelve la misma Promise
  • Internamente reutiliza el mismo elemento canvas y agrega nuevo confetti mientras continúa la animación existente
  • La Promise devuelta por cada llamada se resuelve después de que terminan todas las animaciones

Opciones principales

  • particleCount: cantidad de confetti a lanzar, valor predeterminado 50
  • angle: ángulo de lanzamiento, valor predeterminado 90; 90 significa hacia arriba
  • spread: rango de dispersión desde el centro, valor predeterminado 45
  • startVelocity: velocidad inicial, valor predeterminado 45
  • decay: grado en que disminuye la velocidad, valor predeterminado 0.9
    • Debe mantenerse entre 0 y 1; fuera de ese rango, la velocidad puede aumentar
  • gravity: cuánto se arrastran las partículas hacia abajo, valor predeterminado 1
    • 0.5 es media gravedad, y como no hay límite, también se puede hacer que suban
  • drift: cuánto se desplazan lateralmente, valor predeterminado 0
    • Un valor negativo significa hacia la izquierda y uno positivo hacia la derecha
  • flat: permite desactivar el efecto de inclinación y bamboleo como el confetti 3D real; valor predeterminado false
  • ticks: cantidad de veces que se mueve el confetti, valor predeterminado 200
  • origin: posición inicial del lanzamiento
    • origin.x: posición x de la página; 0 es izquierda, 1 es derecha, valor predeterminado 0.5
    • origin.y: posición y de la página; 0 es arriba, 1 es abajo, valor predeterminado 0.5
  • colors: arreglo de cadenas de color en formato HEX
  • shapes: arreglo de formas de confetti
    • Los valores integrados predeterminados son square, circle y star
    • Por defecto mezcla square y circle en partes iguales
    • Es posible ajustar la proporción de mezcla según la proporción del arreglo, por ejemplo ['circle', 'circle', 'square']
  • scalar: escala de cada partícula, valor predeterminado 1
  • zIndex: capa de visualización del confetti, valor predeterminado 100
  • disableForReducedMotion: deshabilita el confetti para usuarios que prefieren Reduced Motion

Crear formas personalizadas

  • confetti.shapeFromPath({ path, matrix? }) crea una forma de confetti personalizada a partir de un SVG Path string
  • Las formas basadas en Path tienen algunas restricciones
    • Todos los paths se tratan como formas rellenas; los stroke paths no están implementados
    • Los paths se limitan a un solo color
    • Todos los paths necesitan una transform matrix válida
    • Calcular la matrix tiene costo, por lo que conviene calcularla una vez por path durante el desarrollo y cachearla
    • La matrix siempre es la misma para el mismo valor de path
    • Al actualizar la biblioteca, conviene volver a generar y cachear la matrix para mantener la forward compatibility
    • El confetti basado en path se limita a navegadores compatibles con Path2D
  • El valor devuelto es un objeto Shape, que se puede insertar directamente en el arreglo shapes
var triangle = confetti.shapeFromPath({ path: 'M0 10 L5 0 L10 10z' });

confetti({
  shapes: [triangle]
});
  • confetti.shapeFromText({ text, scalar?, color?, fontFamily? }) crea una forma de confetti basada en texto y puede usar emoji Unicode estándar
  • Las formas basadas en texto son adecuadas para emoji confetti
    • Para confetti que se bambolea, generalmente funcionan bien los caracteres individuales cercanos a un cuadrado, especialmente los emoji
    • Como se rasteriza sin dibujar el texto en cada ocasión, si se cambia mucho la escala después de crear la forma, puede verse borrosa
    • Si planeas usar scalar en las opciones de confetti, conviene usar el mismo valor de scalar al crear la shape
  • Las opciones de texto reciben text, scalar, color y fontFamily
    • El valor predeterminado de fontFamily sigue las prácticas nativas del sistema operativo para renderizar emoji y usa sans-serif como fallback
    • Al usar una fuente web, la fuente debe estar cargada antes de renderizar el confetti
var scalar = 2;
var pineapple = confetti.shapeFromText({ text: '🍍', scalar });

confetti({
  shapes: [pineapple],
  scalar
});

Canvas personalizado y renderizado con workers

  • confetti.create(canvas, [globalOptions]) crea una instancia de la función de confetti que usa un canvas específico
  • Es útil cuando quieres limitar el confetti solo a una zona específica de la página
  • De forma predeterminada, este método no modifica el canvas salvo por dibujar en él
  • Aunque cambies el tamaño visible del canvas con CSS, el tamaño real de la imagen del canvas no cambia, por lo que puede estirarse y verse borroso
    • Si activas la opción resize, la biblioteca ajusta el tamaño de la imagen del canvas y responde a cambios de tamaño de la ventana o rotación en móviles
  • No inicialices varias veces instancias de confetti con el mismo elemento canvas; debes conservar la instancia personalizada creada
  • Opciones globales

    • resize: decide si se debe establecer el tamaño de imagen del canvas y mantenerlo ajustado a los cambios de la ventana; valor predeterminado false
    • useWorker: si es posible, renderiza la animación de confetti de forma asíncrona en un web worker; valor predeterminado false
    • Con el valor predeterminado, la animación siempre se ejecuta en el hilo principal
    • Si el navegador lo soporta, la animación se ejecuta fuera del hilo principal para no bloquearlo
    • En navegadores que no lo soportan, este valor se ignora
    • disableForReducedMotion: hace que esa instancia de confetti siempre respete la solicitud de Reduced Motion del usuario
  • Advertencias sobre useWorker: true

    • Al usar useWorker: true, el control del canvas se transfiere al web worker
    • En ese caso, manipular el canvas desde el hilo principal, salvo quitarlo del DOM, provoca errores
    • Si necesitas manipular directamente el canvas, no debes usar la opción useWorker
    var myCanvas = document.createElement('canvas');
    document.body.appendChild(myCanvas);
    
    var myConfetti = confetti.create(myCanvas, {
      resize: true,
      useWorker: true
    });
    myConfetti({
      particleCount: 100,
      spread: 160
    });
    

Detener la animación y patrones de ejemplo

  • confetti.reset() detiene la animación, borra todo el confetti y resuelve de inmediato las Promise pendientes
  • Las instancias separadas creadas con confetti.create() tienen su propio método reset
confetti();

setTimeout(() => {
  confetti.reset();
}, 100);
  • La ejecución básica llama a confetti() sin argumentos
  • Con particleCount: 150 se puede lanzar mucho confetti
  • Con spread: 180 se puede crear confetti con una dispersión amplia
  • Si se usa Math.random() en origin, se pueden crear pequeños efectos de explosión en posiciones aleatorias de la página
  • El ejemplo del README muestra un patrón que usa requestAnimationFrame para lanzar confetti continuamente desde los bordes izquierdo y derecho durante 30 segundos

1 comentarios

 
GN⁺ 2024-04-26
Opiniones en Hacker News
  • El truco para hacer animaciones con buen rendimiento aquí es dibujar en un canvas y luego poner ese canvas por delante de todos los demás elementos, pero desactivando los eventos de puntero para que se pueda seguir interactuando con la página.

    • Correcto. Desactivar los eventos de puntero es sorprendentemente útil.
    • Lo describiste como un truco para animaciones de buen rendimiento, pero no se me ocurre bien otra forma de implementar algo así. ¿Cómo se vería una implementación ingenua?
  • Me recuerda los buenos tiempos de hacer desarrollo web en la secundaria, en 2015. Hice un pequeño sitio web con confeti para preguntarle a una chica si quería ir conmigo al homecoming; visto en retrospectiva, fue bastante nerd.
    En ese entonces, para un niño, hacer un sitio web se sentía como un superpoder. Por la fecha, no creo que haya sido este paquete, pero la animación estaba bastante bien.
    Me encantan estos pequeños proyectos puramente divertidos. Por eso empecé a programar, y sigue siendo una gran motivación para mí.

    • ¿Funcionó? ¿Te dijo que sí?
  • Me gusta esta parte de la página de demo:

    If you happened to get curious and changed the particle count to 400 or so, you saw something disappointing. An even "flattened cone" look to the confetti, making it look way too perfect and ruining the illusion.

    Esta obsesión por el detalle es rara, y cada vez que la encuentro —ya sea en visualización estadística, utilería de cine o confeti en un sitio web— se siente valiosa.
    Como solución, probaría cambiar la distribución aleatoria en sí. Habría que verificarlo, pero sospecho que la distribución en la vida real se parecería más a una distribución gaussiana.

  • Agregamos confeti en el panel de administración cuando un vendedor concreta una venta, y resulta sorprendentemente divertido y motivador.

  • Ojalá hubieran llamado a la función reset confetti.resetti().

    • Como es JavaScript, al menos localmente se puede arreglar fácil con "confetti.resetti = confetti.reset".
      Este enfoque tendrá un pequeño costo de ingeniería de software, pero, como cualquier observador cuidadoso puede ver claramente, los beneficios lo superan por mucho, así que diría que adelante.
    • Hay que darle trabajo a esta persona. Si ya tiene trabajo, al menos una galleta.
    • También se podría abrir un PR.
  • Más allá de ser una biblioteca genial y útil, es un buen ejemplo de un módulo profundo, como lo describe John Ousterhout en Philosophy of Software Design.
    La versión más básica —invocar confeti— es muy fácil de usar, pero si revisas las opciones puedes obtener bastantes cosas: nieve, colores específicos, distintos efectos de confeti, etc.

  • Genial e impresionante.
    Al mismo tiempo, no quiero verlo ejecutándose en ningún sitio web que use. En especial, no quiero confeti acompañando un popup de newsletter o cuando agrego un producto al carrito.

    • Curiosamente, este efecto puede usarse de forma bastante efectiva. No sé sobre esta versión de pantalla completa, pero en un software de gestión de proyectos que usaba un cliente que visité recientemente, al cerrar un ítem el botón se ponía verde y aparecía un efecto de este tipo.
      Era sutil, pero lo bastante visible, y después de la reunión otro desarrollador y yo dijimos algo como “ese efecto estuvo bastante bien”. Transmitía la sensación de “¡bien, hay avance!”.
      Eso sí: basta con hacerlo opcional.

    • Un uso legítimo podría ser algo como el botón de “me gusta” de YouTube. Tiene una animación bonita y, en la app móvil, el dispositivo también vibra. Es una experiencia de usuario muy agradable.

    • https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...

      En el navegador se puede configurar la preferencia de reducir movimiento. Los operadores de sitios y mantenedores de bibliotecas deberían respetarla al implementar cosas como confeti. Esta biblioteca, en particular, tiene la opción disableForReducedMotion.

    • Hay lugares donde un efecto así sí queda bien. Por ejemplo, al completar un juego.

    • Usamos esta biblioteca cuando alguien cumple ciertos requisitos. Le da un efecto bastante bueno al flujo de onboarding.

  • También existe la biblioteca Party.js: https://party.js.org/

    • Entonces, ¿cuál de las dos es más pequeña?
      10.4 kB minificado, 4.2 kB minificado + Gzip
      https://bundlephobia.com/package/canvas-confetti@1.9.2

      28.3 kB minificado, 7.4 kB minificado + Gzip
      https://bundlephobia.com/package/party-js@2.2.0

      Dicho eso, no sé bien cómo funciona bundlephobia. Quizás no muestra de la mejor manera el tamaño final del paquete. Probablemente no refleje code splitting ni importar solo lo necesario. Lo tomo solo como una vista rápida y aproximada.

      En términos de Gzip, parece que confetti gana por algunos KB, así que, salvo que realmente necesites exprimir esos pocos KB, ambas opciones sirven según cuál tenga las funciones que necesitas.

    • El script del artículo original parece tener mucho mejor rendimiento en móvil.

    • La biblioteca del artículo original parece tener un rendimiento mucho mejor. En mi vieja computadora de trabajo, con Party.js ya se siente un pequeño retraso después de apenas 3 clics.
      Con canvas-confetti el retraso recién empieza cuando hago clic sin parar durante varios segundos y ya debo tener más de 30 instancias de confeti con muchas partículas.

  • Resuelvo crucigramas en downforacross.com, y cuando completas un puzzle aparece confeti.
    Tal vez podrían usar parte de este código con mejor rendimiento para que se sienta más ligero.
    Pero, salvo en sitios de “diversión” o usos poco frecuentes, no quiero ver este tipo de animaciones por todas partes.

  • No creo que haga falta poner useful en el título.

    • ¿Qué tal como herramienta de motivación y para verificar que el código compiló?: https://squint-cljs.github.io/squint/
    • Cierto. Aun así, esa palabra me dio curiosidad de verdad, y me dio risa que en realidad no fuera muy útil. Lo recomiendo.
    • Es tan útil como el confeti real; es decir, 100% útil.