5 points par GN⁺ 2024-04-26 | 1 commentaires | Partager sur WhatsApp
  • canvas-confetti est une bibliothèque côté client qui exécute des animations de confettis basées sur canvas sur une page web, avec prise en charge à la fois de l’installation via NPM et de l’inclusion directe par CDN
  • L’API de base confetti() permet, avec un seul objet d’options, d’ajuster le nombre de particules, l’angle, la dispersion, la vitesse, la gravité, les couleurs, les formes, la position, le z-index, etc., et dans les environnements prenant en charge Promise, il est possible de récupérer le moment où l’animation se termine
  • Pour les utilisateurs ayant activé Reduced Motion, l’option disableForReducedMotion est fournie ; sa valeur par défaut est actuellement false, mais cela pourrait changer dans une future version majeure
  • Il est possible de créer des formes personnalisées basées sur des SVG Path et du texte, et d’implémenter des effets comme les emoji confetti en plus des formes intégrées square, circle et star
  • confetti.create() crée une instance sur un canvas spécifique et prend en charge des options globales comme resize et useWorker, mais avec useWorker: true, le contrôle du canvas est transféré à un web worker et toute manipulation depuis le thread principal provoquera une erreur

Installation et modes d’exécution

  • Le fonctionnement de la bibliothèque peut être vérifié sur la page de démonstration
  • Installation possible en tant que package NPM
npm install --save canvas-confetti
  • Dans un build de projet, elle peut être utilisée avec require('canvas-confetti')
  • Cette bibliothèque est un composant client et ne s’exécute pas dans Node
    • Le README indique que le projet doit être buildé avec un outil comme webpack
  • Dans une page HTML, elle peut être incluse directement via un script CDN
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/…;
  • En cas d’utilisation via CDN, il est recommandé d’utiliser la dernière version disponible au moment de l’intégration dans le projet ; l’historique complet des versions est consultable sur la page des releases

Prise en charge de Reduced Motion

  • Certains utilisateurs peuvent ne pas vouloir d’animations sur les sites web, ou préférer qu’elles soient réduites ; le navigateur peut transmettre cette préférence via prefers-reduced-motion
  • L’option disableForReducedMotion permet de ne pas afficher de confettis aux utilisateurs pour qui des animations perturbantes peuvent poser problème
  • La valeur par défaut de cette option est actuellement false
  • Un changement de cette valeur par défaut est envisagé pour une future version majeure, et les avis tranchés peuvent être remontés via une issue
  • Si disableForReducedMotion désactive les confettis, la Promise renvoyée par confetti() est résolue immédiatement

API de base et comportement des Promise

  • Après installation via NPM, elle peut être importée comme composant client dans un build de projet, et dans la version CDN elle est exposée sous la forme de la fonction confetti sur window
  • confetti([options]) accepte un unique objet d’options facultatif
  • Si window.Promise existe, la fonction renvoie une Promise signalant la fin de l’animation
    • Dans des environnements comme IE sans Promise, elle renvoie null
    • Il est possible d’utiliser un polyfill Promise
    • Il est aussi possible de fournir directement une implémentation Promise avec confetti.Promise = MyPromise
  • Si confetti est appelé plusieurs fois avant la fin, la même Promise est renvoyée à chaque fois
  • En interne, le même élément canvas est réutilisé, et de nouveaux confettis sont ajoutés en poursuivant l’animation existante
  • La Promise renvoyée par chaque appel est résolue une fois toutes les animations terminées

Principales options

  • particleCount : nombre de confettis à lancer, valeur par défaut 50
  • angle : angle de lancement, valeur par défaut 90, où 90 signifie vers le haut
  • spread : amplitude de dispersion autour du centre, valeur par défaut 45
  • startVelocity : vitesse initiale, valeur par défaut 45
  • decay : vitesse de ralentissement, valeur par défaut 0.9
    • Doit rester entre 0 et 1, sinon la vitesse peut augmenter
  • gravity : intensité avec laquelle les particules sont attirées vers le bas, valeur par défaut 1
    • 0.5 correspond à une demi-gravité, et comme il n’y a pas de limite il est même possible de les faire monter
  • drift : dérive latérale, valeur par défaut 0
    • Une valeur négative signifie vers la gauche, positive vers la droite
  • flat : permet de désactiver l’effet d’inclinaison et d’oscillation des confettis 3D réalistes, valeur par défaut false
  • ticks : nombre de mouvements des confettis, valeur par défaut 200
  • origin : position de départ du lancement
    • origin.x : position x sur la page, 0 à gauche, 1 à droite, valeur par défaut 0.5
    • origin.y : position y sur la page, 0 en haut, 1 en bas, valeur par défaut 0.5
  • colors : tableau de chaînes de couleur au format HEX
  • shapes : tableau des formes de confettis
    • Les valeurs intégrées par défaut sont square, circle, star
    • Par défaut, square et circle sont mélangés à parts égales
    • Il est possible d’ajuster le ratio du mélange avec un tableau comme ['circle', 'circle', 'square']
  • scalar : échelle de chaque particule, valeur par défaut 1
  • zIndex : couche d’affichage des confettis, valeur par défaut 100
  • disableForReducedMotion : désactive les confettis pour les utilisateurs préférant Reduced Motion

Créer des formes personnalisées

  • confetti.shapeFromPath({ path, matrix? }) crée une forme de confetti personnalisée à partir d’une chaîne SVG Path
  • Les formes basées sur Path ont quelques contraintes
    • Tous les path sont traités comme des formes pleines, les stroke path ne sont pas implémentés
    • Les path sont limités à une seule couleur
    • Tous les path nécessitent une matrice de transformation valide
    • Le calcul de la matrice ayant un coût, il est conseillé de la calculer une fois par path pendant le développement et de la mettre en cache
    • La matrice est toujours identique pour une même valeur de path
    • Lors d’une mise à jour de la bibliothèque, il est recommandé de régénérer et remettre en cache la matrice pour assurer la compatibilité future
    • Les confettis basés sur Path sont limités aux navigateurs prenant en charge Path2D
  • La valeur de retour est un objet Shape, qui peut être utilisé directement dans le tableau shapes
var triangle = confetti.shapeFromPath({ path: 'M0 10 L5 0 L10 10z' });

confetti({
  shapes: [triangle]
});
  • confetti.shapeFromText({ text, scalar?, color?, fontFamily? }) crée des formes de confettis basées sur du texte, avec prise en charge des emoji Unicode standards
  • Les formes basées sur du texte conviennent particulièrement aux emoji confetti
    • Pour l’effet de confettis oscillants, les caractères uniques proches d’un carré, en particulier les emoji, fonctionnent généralement bien
    • Comme le texte est rastérisé plutôt que redessiné à chaque fois, un fort changement d’échelle après création peut produire un rendu flou
    • Si vous prévoyez d’utiliser l’option scalar des confettis, il est préférable d’utiliser la même valeur scalar lors de la création de la forme
  • Les options texte acceptent text, scalar, color, fontFamily
    • La valeur par défaut de fontFamily suit les conventions natives de rendu des emoji du système d’exploitation, avec sans-serif en fallback
    • En cas d’utilisation d’une webfont, celle-ci doit être chargée avant le rendu des confettis
var scalar = 2;
var pineapple = confetti.shapeFromText({ text: '🍍', scalar });

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

Canvas personnalisé et rendu via worker

  • confetti.create(canvas, [globalOptions]) crée une instance de la fonction confetti utilisant un canvas spécifique
  • C’est utile lorsqu’on veut limiter les confettis à une zone précise de la page
  • Par défaut, cette méthode ne modifie pas le canvas en dehors du dessin lui-même
  • Si la taille d’affichage du canvas est modifiée via CSS, la taille réelle de l’image du canvas ne change pas, ce qui peut l’étirer et la rendre floue
    • En activant l’option resize, la bibliothèque ajuste la taille de l’image du canvas et suit également les changements de taille de fenêtre ou la rotation sur mobile
  • Il ne faut pas initialiser plusieurs fois une instance de confetti sur le même élément canvas ; il faut conserver et réutiliser l’instance personnalisée créée
  • Options globales

    • resize : détermine s’il faut définir la taille de l’image du canvas et la maintenir synchronisée avec les changements de fenêtre, valeur par défaut false
    • useWorker : rend l’animation des confettis dans un web worker asynchrone quand c’est possible, valeur par défaut false
    • Par défaut, l’animation s’exécute toujours sur le thread principal
    • Si le navigateur le prend en charge, l’animation s’exécute hors du thread principal afin de ne pas le bloquer
    • Dans les navigateurs non compatibles, cette valeur est ignorée
    • disableForReducedMotion : garantit que cette instance de confetti respecte toujours la demande Reduced Motion de l’utilisateur
  • Points d’attention pour useWorker: true

    • Avec useWorker: true, le contrôle du canvas est transféré à un web worker
    • Dans ce cas, toute manipulation depuis le thread principal, à l’exception de la suppression du canvas du DOM, provoque une erreur
    • Si le canvas doit être manipulé directement, il ne faut pas utiliser l’option useWorker
    var myCanvas = document.createElement('canvas');
    document.body.appendChild(myCanvas);
    
    var myConfetti = confetti.create(myCanvas, {
      resize: true,
      useWorker: true
    });
    myConfetti({
      particleCount: 100,
      spread: 160
    });
    

Arrêt de l’animation et exemples de patterns

  • confetti.reset() arrête l’animation, efface tous les confettis et résout immédiatement toute Promise en attente
  • Les instances séparées créées avec confetti.create() disposent de leur propre méthode reset
confetti();

setTimeout(() => {
  confetti.reset();
}, 100);
  • L’exécution de base consiste à appeler confetti() sans argument
  • Il est possible de lancer beaucoup de confettis avec particleCount: 150
  • spread: 180 permet de créer des confettis très dispersés
  • En utilisant Math.random() dans origin, on peut créer de petites explosions à des positions aléatoires sur la page
  • L’exemple du README montre un pattern utilisant requestAnimationFrame pour lancer en continu des confettis depuis les bords gauche et droit pendant 30 secondes

1 commentaires

 
GN⁺ 2024-04-26
Commentaires Hacker News
  • L’astuce pour faire ici une animation performante consiste à dessiner sur un canvas, puis à placer ce canvas devant tous les autres éléments tout en désactivant les pointer events pour pouvoir continuer à interagir avec la page

    • Oui. La désactivation des pointer events est étonnamment utile
    • C’est présenté comme une astuce pour faire des animations performantes, mais je ne vois pas vraiment d’autre manière d’implémenter ça. À quoi ressemblerait une implémentation naïve ?
  • Ça me rappelle les bons vieux jours où je faisais du développement web au lycée, en 2015. J’avais créé un petit site web avec des confettis pour demander à une fille si elle voulait aller au bal de promo avec moi, et avec le recul c’était incroyablement geek
    À l’époque, pour un gamin, créer un site web ressemblait à un super-pouvoir. Je ne pense pas que c’était ce package vu la période, mais l’animation était plutôt réussie
    J’adore ce genre de petits projets faits juste pour le plaisir. C’est pour ça que j’ai commencé à programmer, et ça reste encore aujourd’hui une grande source de motivation

    • Ça a marché ? Elle a dit oui ?
  • J’aime bien ce passage de la page de démo :

    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.

    Ce genre d’obsession du détail est rare, et que ce soit dans une visualisation statistique, un accessoire de film ou des confettis sur un site web, je trouve ça toujours précieux quand j’en vois
    Comme solution, j’aurais envie de modifier la distribution aléatoire elle-même. Je vérifierais en pratique, mais j’ai l’intuition qu’en réalité la distribution se rapproche davantage d’une distribution gaussienne

  • J’ai ajouté des confettis dans le tableau de bord admin quand un commercial conclut une vente, et c’est étonnamment amusant et motivant

  • J’aurais aimé que la fonction reset s’appelle confetti.resetti()

    • Comme c’est du JavaScript, au moins en local on peut facilement corriger ça avec "confetti.resetti = confetti.reset"
      Cette approche a peut-être un petit coût en ingénierie logicielle, mais comme tout observateur attentif pourra le constater, les bénéfices l’emportent de façon écrasante, donc à mon avis il faut le faire
    • Il faut donner un travail à cette personne. Si elle en a déjà un, il faut au moins lui offrir des cookies
    • On pourrait faire une PR
  • Indépendamment du fait que ce soit une bibliothèque sympa et utile, c’est un bon exemple de module profond au sens où John Ousterhout l’entend dans Philosophy of Software Design
    La version la plus basique, c’est-à-dire déclencher des confettis, est très facile à utiliser, mais quand on regarde les options, on obtient pas mal de choses : neige, couleurs spécifiques, différents effets de confettis, etc.

  • C’est cool et impressionnant
    En même temps, je n’ai aucune envie de voir ça se déclencher sur les sites web que j’utilise. Surtout pas pour une popup de newsletter ou quand j’ajoute un produit au panier

    • Curieusement, cet effet peut être utilisé de manière assez efficace. Je ne sais pas pour cette version plein écran, mais un logiciel de gestion de projet utilisé récemment par un client faisait passer le bouton au vert avec un effet de ce genre quand on clôturait un élément
      C’était discret mais assez visible, et après la réunion un autre développeur et moi nous sommes tous les deux dit : « c’était plutôt un bon effet »
      Ça donnait une sensation de « OK, il y a du progrès ! »
      Il suffit simplement de le rendre optionnel

    • Un cas d’usage légitime serait peut-être le bouton J’aime de YouTube. Il y a une belle animation, et sur l’application mobile l’appareil vibre aussi. C’est une expérience utilisateur très satisfaisante

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

      On peut configurer le navigateur pour indiquer une préférence pour la réduction des animations. Les exploitants de sites et les mainteneurs de bibliothèques devraient respecter ce choix lorsqu’ils implémentent des choses comme des confettis. Cette bibliothèque propose notamment l’option disableForReducedMotion

    • Il y a des endroits où ce genre d’effet a sa place. Par exemple à la fin d’un jeu

    • Nous utilisons cette bibliothèque quand quelqu’un remplit certains critères. Ça produit un effet plutôt sympa dans le parcours d’onboarding

  • Il y a aussi la bibliothèque Party.js : https://party.js.org/

    • Dans ce cas, laquelle est la plus légère ?
      10.4 kB minifié, 4.2kB minifié + Gzip
      https://bundlephobia.com/package/canvas-confetti@1.9.2

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

      Cela dit, je ne sais pas vraiment comment fonctionne bundlephobia. Ce n’est peut-être pas la meilleure représentation de la taille finale d’un package. J’imagine que ça ne prend probablement pas en compte le code splitting ou le fait de n’importer que ce dont on a besoin. Je l’utilise juste comme aperçu rapide et approximatif

      En taille Gzip, confetti gagne de quelques kB, donc à moins de devoir vraiment gratter ces quelques kB, les deux peuvent convenir selon les fonctionnalités dont on a besoin

    • Le script de l’article original semble bien plus performant sur mobile

    • La bibliothèque de l’article original semble bien plus performante. Sur mon vieux PC de travail, Party.js commence à montrer un peu de latence après seulement 3 clics
      canvas-confetti ne commence à ralentir qu’après plusieurs secondes de clics ininterrompus, quand on arrive probablement à plus de 30 instances de confettis et beaucoup de particules

  • Je fais des mots croisés sur downforacross.com, et quand on termine une grille il y a des confettis
    Ils pourraient peut-être utiliser une partie du code plus performant présenté ici pour rendre ça plus léger
    Mais en dehors d’un site « fun » ou d’un usage rare, je n’ai pas envie de voir ce genre d’animation partout

  • Je ne pense pas qu’il soit nécessaire d’ajouter useful dans le titre

    • Comme outil de motivation et moyen de vérifier que le code a bien compilé, que penses-tu de ça : https://squint-cljs.github.io/squint/
    • Oui. Cela dit, c’est ce mot qui a vraiment éveillé ma curiosité, et j’ai trouvé ça drôle que ce ne soit en réalité pas très utile. Recommandé
    • C’est aussi utile que les vrais confettis, donc utile à 100 %