Quâest-ce que Lottie ? đ§
Lottie est un format dâanimation vectorielle se basant sur des donnĂ©es au format JSON. Il a Ă©té créé en 2015 par la sociĂ©tĂ© amĂ©ricaine Airbnb.
Airbnb utilise beaucoup dâanimations dans son parcours utilisateur et il Ă©tait souvent problĂ©matique pour les dĂ©veloppeurs dâintĂ©grer correctement les animations faites par les designers. Les GIFs fournis Ă©taient difficilement adaptables Ă la rĂ©solution dâĂ©cran de lâutilisateur et mettait parfois du temps Ă se charger.
Partant de ce constat, Airbnb a dĂ©veloppĂ© un plugin sur le logiciel Adobe After Effects baptisĂ© Bodymovin. Ce plugin permet dâexporter des animations dans un fichier au format JSON.
Le fichier au format JSON peut ensuite ĂȘtre interprĂ©tĂ© par les diffĂ©rentes librairies proposĂ©es par Lottie : sur un navigateur web, sur une application tĂ©lĂ©phone native ou encore sur une application Windows.
Les avantages de Lottie sont nombreux :
- Le format est multi-plateforme. Le fichier JSON reste inchangé entre les différentes plateformes cibles.
- La rĂ©solution des images sâadapte en fonction de la taille de lâĂ©cran de lâutilisateur puisque les animations sont au format vectoriel.
- Une animation Lottie est beaucoup plus lĂ©gĂšre quâune sĂ©quence dâimages PNG ou encore un GIF.
âIf a PNG is a T-Rex, and a GIF is an elephant, then a Lottie is a puppy.â
- Le format Lottie est beaucoup plus portable que des fichiers SVG animés en CSS.
- Lottie offre Ă©galement une interface web de modification des animations. Il est donc possible de changer un Ă©lĂ©ment graphique de lâanimation puis de rĂ©exporter le fichier JSON associĂ©.
Notre besoin chez Indy đ»
Chez Indy, nous souhaitons proposer à nos utilisateurs une expérience moderne et intuitive afin de faciliter leur compréhension de la comptabilité, une notion parfois difficile à appréhender.
Le but Ă©tait dâannoncer une nouvelle fonctionnalitĂ© dans lâapplication via une animation Lottie.
RĂ©alisation technique de lâintĂ©gration de lâanimation đ ïž
CrĂ©ation dâun composant Vue gĂ©nĂ©rique đïž
Nous avons créé un composant gĂ©nĂ©rique responsable du chargement de lâanimation via la mĂ©thode loadAnimation
de la bibliothĂšque lottie-web
. Lâutilisation dâanimation Lottie dans lâapplication passe obligatoirement par ce composant.
Ce composant prend en compte 3 props :
lottieJsonPath
: le chemin vers la source de données JSON. Ce paramÚtre est évidemment obligatoire.loopAnimation
: lâanimation doit-elle ĂȘtre jouĂ©e en boucle ? ParamĂštre optionnel.autoPlayAnimation
: lâanimation doit-elle ĂȘtre jouĂ©e Ă lâinitialisation du composant ? ParamĂštre optionnel.
La mĂ©thode loadAnimation peut Ă©galement prendre en compte dâautres paramĂštres (cf. la documentation), mais nous jugions que ceux-ci Ă©taient inutiles dans notre utilisation. En effet chez Indy, nous respectons le principe YAGNI (« You ainât gonna need it », qui peut se traduire par « vous nâen aurez pas besoin »). Nous Ă©vitons au maximum lâimportation de librairies inutiles et lâimplĂ©mentation de code non utilisĂ©s dans lâapplication.
Voici ce Ă quoi ressemble le composant LottieAnimation :
<template> <div ref="animationContainer" /> </template> <script> export default { name: 'LottieAnimation', props: { lottieJsonPath: { // the JSON path linked to the animation type: String, required: true, }, loopAnimation: { // Do we want the animation to loop? type: Boolean, required: false, default: true, }, autoPlayAnimation: { // Do we want to play the animation on initialization? type: Boolean, required: false, default: true, }, }, data: () => ({ rendererSettings: { scaleMode: 'centerCrop', clearCanvas: true, progressiveLoad: false, hideOnTransparent: true, }, lottieAnimation: undefined, }), async mounted() { await this.init(); }, beforeDestroy() { this.lottieAnimation?.destroy(); }, methods: { [...] }, }; </script>
â ïž Il est important dâappeler la mĂ©thode destroy
sur lâobjet contenant lâanimation dans le hook beforeDestroy
de Vue pour éviter les fuites de mémoire.
Lazy loading de la bibliothĂšque lottie-web âïž
La bibliothÚque lottie-web est une bibliothÚque plutÎt lourde (taille gzipped à 67.3 Ko). Ceci est particuliÚrement impactant si la bibliothÚque est située dans le chunk principal lors du build.
Le chargement de la page principale pour un utilisateur nâayant pas une bonne connexion prendrait un temps beaucoup plus long dans ce cas. La bibliothĂšque serait quand mĂȘme chargĂ©e, mĂȘme si des pages nâaffichent pas dâanimation Lottie.
Dans notre cas dâutilisation chez Indy, nous utilisons Lottie pour lâinstant quâĂ un seul endroit. Nous avons souhaitĂ© lazy loader la bibliothĂšque pour quâelle ne soit chargĂ©e que quand lâutilisateur ouvre la page contenant lâanimation. Dans sa navigation sur les autres pages, la bibliothĂšque nâest pas chargĂ©e.
Pour lazy loader lottie, nous avons créé un chunk contenant seulement la bibliothĂšque. La bibliothĂšque est en mode prefetch, câest Ă dire quâelle ne se charge que quand le navigateur est disponible pour effectuer le chargement.
Voici à quoi ressemble la méthode init()
de notre composant LottieAnimation. Elle sâoccupe de lazy loader la bibliothĂšque Lottie puis de configurer lâanimation Lottie.
methods: { async init() { const lottie = await import( /* webpackChunkName: "lottie" */ /* webpackMode: "lazy" */ /* webpackPrefetch: true */ 'lottie-web' ); this.lottieAnimation = lottie.loadAnimation({ container: this.$refs.animationContainer, renderer: 'svg', loop: this.loopAnimation, autoplay: this.autoPlayAnimation, path: this.lottieJsonPath, rendererSettings: this.rendererSettings, }); }, },
â ïž La rĂ©fĂ©rence au container dans la mĂ©thode loadAnimation
ne doit pas pointer sur un élément HTML contenant des directives Vue telles que v-if
. Autrement, le mapping ne peut pas se faire.
Code complet du composant LottieAnimation đšđœâđ»
<template> <div ref="animationContainer" /> </template> <script> export default { name: 'LottieAnimation', props: { lottieJsonPath: { // the JSON path linked to the animation type: String, required: true, }, loopAnimation: { // Do we want the animation to loop ? type: Boolean, required: false, default: true, }, autoPlayAnimation: { // Do we want to play the animation on initialization ? type: Boolean, required: false, default: true, }, }, data: () => ({ rendererSettings: { scaleMode: 'centerCrop', clearCanvas: true, progressiveLoad: false, hideOnTransparent: true, }, lottieAnimation: undefined, }), async mounted() { await this.init(); }, beforeDestroy() { this.lottieAnimation?.destroy(); }, methods: { async init() { const lottie = await import( /* webpackChunkName: "lottie" */ /* webpackMode: "lazy" */ /* webpackPrefetch: true */ 'lottie-web' ); this.lottieAnimation = lottie.loadAnimation({ container: this.$refs.animationContainer, renderer: 'svg', loop: this.loopAnimation, autoplay: this.autoPlayAnimation, path: this.lottieJsonPath, rendererSettings: this.rendererSettings, }); }, }, }; </script>