Lottie – Pousser l’interaction jusqu’à ses retranchements

Lottie est un format d’animation vectorielle basé sur JSON (JavaScript Object Notation), permettant de dynamiser et rendre plus interactif un site web ou application mobile. Animer une interface procure pour l’utilisateur de l’émotion et crée de l’empathie en liant le monde réel et numérique à travers une expérience divertissante, humaine et engageante.

Cette solution apporte des avantages considérables :

  • une complexité d’intégration minime
  • une taille de fichiers réduite par rapport aux videos ou GIF
  • un format de fichier universel et plus maintenable
  • une qualité de rendu toujours au top grâce à l’utilisation de formes vectorielles
  • motion design de grande qualité

Initialement prévues pour être lues sur iOS, Android et ReactNative, les animations Lottie sont aujourd’hui intégrables sur de nombreux autres systèmes : Flutter, Xamarin, Native Script, Windows, Vue, Angular, QT, … Des bibliothèques et des plug-ins facilitent cette intégration.

Pour avoir une expérience utilisateur plus riche, l’animation peut être synchronisée avec les gestes utilisateur (au clic, au survol…). Mais jusqu’où peut-on pousser cette interaction ?
Durant notre atelier pratique, nous nous sommes focalisés sur l’intégration de Lottie et surtout sur deux types d’interactions, à savoir :

  • d’une part en agissant sur la progression de l’animation
  • d’autre part en agissant sur les formes et leurs propriétés

Les travaux ont été réalisés par deux groupes en parallèle, l’un en JavaScript, l’autre en Flutter.

Simplicité d’intégration d’un Lottie

JavaScript

Ajouter un Lottie dans vos applications développées en JavaScript se fait de manière simple. Dans cet exemple, nous utiliserons la librairie lottie-web proposée par AirBnB.

  1. Ajouter la librairie au projet

Depuis un CDN, en ajoutant le lien dans le header de votre page HTML

<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.9.6/lottie.min.js"></script>
</head>

Avec NPM, vous pouvez utiliser la commande : npm install lottie-web

  1. Ajouter un container qui contiendra notre Lottie
<div id="container" class="container"></div>

  1. Intégration du Lottie :

Depuis un fichier statique dans les assets du projet :

let container = document.getElementById("container");

  lottie.loadAnimation({
   container: container,
   renderer: 'svg', 
   loop: false,
   autoplay: false,
   path: 'assets/lf20_vktpsg4v.json' 
});

      4. Depuis une URL internet :

let container = document.getElementById("container");

lottie.loadAnimation({
  container: container,
  renderer: 'svg',
  loop: false,
  autoplay: false,
  path: 'https://assets4.lottiefiles.com/packages/lf20_vktpsg4v.json'
});

Flutter

Intégrer un Lottie dans une application développée avec Flutter se fait assez simplement en quelques étapes, en utilisant la librairie lottie, disponible sur pub.dev :

  1. Ajout de la dépendance dans le fichier yaml : lottie: ^1.4.0
  2. Import du package : import ‘package:lottie/lottie.dart’;
  3. Intégration du Lottie :

Depuis un fichier statique dans les assets du projet :

Lottie.asset(
      'assets/lf20_vktpsg4v.json',
)

Depuis une URL internet :

Lottie.network(
      'https://assets4.lottiefiles.com/packages/lf20_vktpsg4v.json',
)

Dans les deux cas, le widget Lottie chargera le fichier json et exécutera l’animation indéfiniment.

Voici finalement à quoi ressemble le code qui permet d’afficher le rendu visible juste au-dessus (un Lottie depuis les assets locaux et l’autre depuis internet) :

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; 

class LottieWidget extends StatelessWidget {
  const LottieWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          LottieBuilder.asset(
            'assets/lf20_sgnacf85.json',
          ),
          LottieBuilder.network(
            'https://assets4.lottiefiles.com/packages/lf20_vktpsg4v.json',
          ),
        ],
      );
}

Lancement d’un Lottie

Comme nous venons de le voir, une intégration simple d’un Lottie déclenche de manière automatique l’animation dès qu’elle est buildée. Cependant, il peut être nécessaire dans certains cas de devoir déclencher l’animation manuellement. On pourrait choisir que l’animation évolue sur des gestes utilisateurs, comme par exemple :

  • Au clic sur un élément de l’écran
  • Au scroll de la page
  • Au survol d’un élément de la page

Nous considérerons pour l’exemple que le scroll de la page fait évoluer l’avancement du Lottie. Le niveau de scroll permet de définir la frame du Lottie à afficher. Le rendu final est le suivant :

JavaScript

Que ce soit pour lottie-web de AirBnB ou lottie-player de LottieFiles, les deux solutions nécessiteront du développement spécifique pour relier la progression du lottie à un évènement utilisateur.Plusieurs librairies javascript existent pour simplifier ce cas de figure, en fonction de l’évènement.Ici pour l’évènement du scroll, on présente deux solutions : basicScroll et lottie-interactivity.

Lottie-player + basicScroll

Une librairie javascript tierce, basic Scroll, permet aussi de relier un élément à l'action du scroll de manière très simple.

La propriété inside dans la fonction basicScroll.create relie la progression du lottie à celle du scroll.

<html>
    <head>
        <script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
        <script src="https://s.electerious.com/basicScroll/dist/basicScroll.min.js"></script>
    </head>
    <body>
        <section id="section" style="width: 100vw; text-align: center;">
            <lottie-player id="container" style="position: fixed; z-index: -1;"></lottie-player>
        </section>
    </body>
</html>

<script>
    const container = document.getElementById("container");
    const section = document.getElementById("section");

    for (let index = 0; index < 75; index++) {
        let newP = document.createElement("p");
        let textNode = document.createTextNode("Lorem ipsum "+index);
        newP.appendChild(textNode);
        section.appendChild(newP);
    }
    const player = container;
    player.load("https://assets3.lottiefiles.com/packages/lf20_y2tcu7p4.json");

    const lottiediv = basicScroll
    .create({
        elem: container,
        from: "top-middle",
        to: "bottom-top",
        direct: true,
        inside: (instance, percentage, props) => {
        player.seek(Math.round(percentage) + "%");
        }
    })
    .start();
</script>

Lottie-player + lottie-interactivity

La librairie lottie-interactivity est une solution par LottieFiles, et s’importe en plus du lottie-player (par LottieFiles).Lottie-player permettra l’usage de la balise <lottie-player> pour encapsuler le lottie, et lottie-ineractivity ajoutera la possibilité de jouer sur la progression.De manière très similaire à la solution précédente, le mode: »scroll » dans la fonction LottieInteractivity.create relie la progression du lottie, designé par son id dans la propriété player, à celle du scroll.

Flutter

Cet exemple de code montre comment prendre, en Flutter, le contrôle total de l’animation en fournissant au widget LottieBuilder un contrôleur de type AnimationController (comme pour une animation classique en Flutter).

Avec ce contrôleur, nous pouvons interagir avec l’animation comme nous le souhaitons : jouer l’animation en avant ou en arrière, boucler entre des frames spécifiques, déclencher l’animation jusqu’à sa fin, l’arrêter…

Dans l’extrait de code ci-dessous, dès que l’utilisateur scroll sur la ListView, la valeur du contrôleur _lottieController est modifiée pour faire avancer les frames du Lottie jusqu’à une position précise.

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; 

class LottieControlWidget extends StatefulWidget {
  const LottieControlWidget({Key? key}) : super(key: key);
 
  @override
  State<LottieControlWidget> createState() => _LottieControlWidgetState();
} 

class _LottieControlWidgetState extends State<LottieControlWidget>
    with TickerProviderStateMixin {
  late final AnimationController _lottieController =
      AnimationController(vsync: this);
  final ScrollController _scrollController = ScrollController();
 
  @override
  void initState() {
    _scrollController.addListener(() {
      _lottieController.value =
          _scrollController.offset / _scrollController.position.maxScrollExtent;
    });
    super.initState();
  }
 
  @override
  Widget build(BuildContext context) => Stack(
        fit: StackFit.expand,
        children: [
          LottieBuilder.network(
            'https://assets3.lottiefiles.com/packages/lf20_y2tcu7p4.json',
            fit: BoxFit.cover,
            frameRate: FrameRate.max,
            controller: _lottieController,
            onLoaded: (animation) =>
                _lottieController.duration = animation.duration,
          ),
          ListView(
            controller: _scrollController,
            children: List.generate(
              75,
              (index) => Center(
                child: Padding(
                  padding: const EdgeInsets.all(10),
                  child: Text(
                    'Lorem ipsum $index',
                  ),
                ),
              ),
            ),
          )
        ],
      );
}

Interactions globales sur le Lottie

Le scroll n’est pas le seul déclencheur de l’avancement de l’animation. D’autres évènements permettent de la rendre interactive ou de changer son déroulement. Nous verrons entre autres comment dérouler :

  • un extrait jusqu’à une image précise
  • au click

JavaScript

En utilisant toujours la librairie Lottie-interactivity, on peut jouer sur les paramètres mis à disposition pour changer les déclencheurs et la progression de l’animation.

Dérouler l’animation au click

Le déclencheur peut être le click sur le lottie ou sur tout autre élément, comme ci-dessous :

<html>
  <head>
      <script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
      <script src="https://unpkg.com/@lottiefiles/lottie-interactivity@latest/dist/lottie-interactivity.min.js"></script>
  </head>
  <body>
      <section id="section" >
          <p>Click on the section below</p>
          <lottie-player id="container" style="max-height:500px; background-color: lightyellow;"></lottie-player>
      </section>
  </body>
</html> 

<script>
    const container = document.getElementById('container');
 
    const player = container;
    player.load("https://assets5.lottiefiles.com/packages/lf20_bcjfw1k6.json"); 

    LottieInteractivity.create({
        player: container,
        mode:"cursor",
        actions: [
            {
                type: "click",
                forceFlag: false
            }
        ]
    });
</script>

Dérouler un extrait de l’animation

Exemple d’une animation exécutée de l’image 30 à la 150, avec lottie-player uniquement.

<html>
  <head>
      <script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
  </head>
  <body>
      <section id="section" >
          <p>Click on the section below</p>
          <lottie-player id="container" style="max-height:500px; background-color: lightyellow;"></lottie-player>
      </section>
  </body>
</html>
 
<script>
    const container = document.getElementById('container');
    const player = container;
    player.load("https://assets5.lottiefiles.com/packages/lf20_bcjfw1k6.json");
 
    const lottie = container.getLottie();
    lottie.goToAndStop(30, true);
    container.addEventListener("click", function() {
      lottie.playSegments([30, 150], true);
    }); 
</script>

Dérouler une animation en sens inverse

Exemple d’une animation se déroulant normalement au survol, mais en sens inverse en éloignant le curseur de la zone l’animation.<html>

Flutter

Il est très simple d’interrompre l’animation d’un Lottie à un moment choisi. Ce qu’illustre le code ci-dessous, où un Listener est ajouté au Controller du Lottie.

On peut noter également qu’il est aisé de déclencher l’animation du Lottie au clic.

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; 

class LottieEndFrame extends StatefulWidget {
  const LottieEndFrame({Key? key}) : super(key: key); 

  @override
  State<LottieEndFrame> createState() => _LottieEndFrameState();
} 

class _LottieEndFrameState extends State<LottieEndFrame> with TickerProviderStateMixin {
  late final AnimationController _controller;
  late double _animationEnd; 

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    _controller.addListener(() {
      if (_controller.value >= _animationEnd) {
        _controller.reset();
      }
    });
    super.initState();
  } 

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  } 

  void setEndFrame(AnimationController controller, double end) {
    _animationEnd = end;
    _controller.forward();
  } 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Lottie.network(
            'https://assets5.lottiefiles.com/packages/lf20_bcjfw1k6.json',
            controller: _controller,
            onLoaded: (composition) => _controller.duration = composition.duration

          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                child: const Text('stop Lottie at 55%'),
                onPressed: () => setEndFrame(_controller, 0.55),
              ),
              ElevatedButton(
                child: const Text('stop Lottie at 100%'),
                onPressed: () => setEndFrame(_controller, 1),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

Une autre interaction globale qui peut être utile, est celle de jouer le Lottie en sens inverse. Toujours grâce à un Controller, l’opération n’est pas compliquée.

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
 
class LottieReverse extends StatefulWidget {
  const LottieReverse({Key? key}) : super(key: key);
 
  @override
  State<LottieReverse> createState() => _LottieReverseState();
}
 
class _LottieReverseState extends State<LottieReverse> with TickerProviderStateMixin {
  late final AnimationController _controller; 

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    super.initState();
  } 

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  } 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Lottie.network(
              'https://assets5.lottiefiles.com/packages/lf20_bcjfw1k6.json',
              controller: _controller,
              onLoaded: (composition) => _controller.duration = composition.duration
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                child: const Text('play FORWARD'),
                onPressed: () async {
                  await _controller.forward();
                  _controller.reset();
                },
              ),
              ElevatedButton(
                child: const Text('play in REVERSE'),
                onPressed: () async {
                  _controller.value = 1;
                  await _controller.reverse();
                  _controller.reset();
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

Interactions sur une forme / propriété du Lottie

Les possibilités d’interaction avec un Lottie ne se limitent pas à un niveau global. La structure en JSON nous permet d’intervenir sur de nombreux paramètres des différentes parties d’un Lottie, cela sans impacter le reste. Cette particularité s’avère très intéressante: il s’agit d’aller plus loin que la simple intégration d’une animation à une page.Il est à préciser que le Lottie doit être convenablement conçu par le graphiste pour donner les pleins pouvoirs au développeur.

JavaScript

Changer l’opacité d'un élément du lottie

En enregistrant le lottie suivant en local : https://assets2.lottiefiles.com/packages/lf20_tuniawqh.jsonon peut parcourir ses propriétés jusqu’à arriver à celle qui nous intéresse.Ici la couche layers[4] dans assets[0] correspond au cercle externe jaune du lottie.. On accède à son opacité via la propriété o pour opacity.

<html>
    <head>
      <script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script>
    </head>
    <body>
        <div id="container"></div>
    </body></html>
 
<script>
    const container = document.getElementById('container');
    fetch('./lottie.json')
    .then(response => response.json())
    .then(data => renderAnimation(data))
    .catch(error => console.log(error));

    function renderAnimation(data) {
        const myJson = data; 

        myJson.assets[0].layers[4].ks.o.k = 0; 

        const res = JSON.stringify(myJson).replaceAll('"', '"');
        container.innerHTML =
            `<lottie-player autoplay loop mode="normal" src="`
            +
            res
            +
            `" style="width: 350px">
            </lottie-player>`;
    }
</script>

Flutter

Les couleurs de certaines parties d’un Lottie peuvent être modifiées en fonction du design. Ci-dessous, un Lottie évolue (couleurs de la face et de la larme) en fonction du thème adopté.

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; 

class LottieThemeColor extends StatelessWidget {
  const LottieThemeColor({Key? key, required this.theme}) : super(key: key);
  final ThemeData theme; 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Adapting Lottie\'s color to theme'),),
      body: Center(
        child: Lottie.network(
          'https://assets2.lottiefiles.com/packages/lf20_tuniawqh.json',
          delegates: LottieDelegates(
            values: [
              ValueDelegate.color(
                const ['main', 'Layer 2', 'Group 1', 'Fill 1'],   // tear
                value: theme.primaryColor == Colors.blue ? Colors.yellow : Colors.blue,
              ),
              ValueDelegate.color(
                const ['main', 'face', 'Group 1', 'Fill 1'],    // face
                value: theme.primaryColor == Colors.blue ? Colors.blue : Colors.yellow,
              ),
            ]
          ),
        ),
      ),
    );
  }
}

De la même manière, on peut par exemple modifier l’opacité d’une partie d’un Lottie. Ici on a fait disparaître la face du personnage.

body: Center(
        child: Lottie.network(
          'https://assets2.lottiefiles.com/packages/lf20_tuniawqh.json',
          delegates: LottieDelegates(
              values: [
                ValueDelegate.opacity(
                  const ['main', 'face', 'Group 1', 'Fill 1'],
                  value: 0,
                ),
              ]
          ),
        ),
      ),

Limites de Lottie

Nous venons de voir qu’il est possible d’interagir sur une forme bien précise du Lottie, cependant nous nous heurtons à quelques difficultés.

Identification de la forme

Il n’est pas forcément chose aisée d’identifier dans le code, la forme sur laquelle nous souhaitons agir. Pour ce faire, il faut pouvoir déchiffrer le fichier JSON qui est très abstrait et s’y repérer.

Conception du Lottie

Étant donné que la création du Lottie est la responsabilité du designer/graphiste, il existe différentes versions d’un fichier JSON pour un même Lottie. Les couches et objets du Lottie sont alors plus ou moins bien architecturés, ce qui peut faciliter le travail des développeurs pour interagir avec ou au contraire, rendre la tâche très complexe.

Positionnement des formes

Dans certains cas, nous voudrions peut-être détecter des gestes utilisateurs contenus dans une forme pour pouvoir interagir avec celle-ci précisément. Malheureusement, il est assez complexe de pouvoir obtenir une information précise du placement (X, Y) d’une forme à un instant T du Lottie.

Nous préconisons donc l’utilisation de solutions de contournement dans la mesure du possible. Par exemple, appliquer un gesture au-dessus du Lottie – à la taille du Lottie général – qui capte les gestes utilisateurs et exécute le lottie jusqu’à une certaine frame donnée

Synthèse

En résumé, nous avons pu voir à travers cet article que Lottie est un très bon moyen d’intégrer des animations et de rendre plus vivante une application mobile/web.

C’est une solution simple à mettre en place sans avoir besoin de connaissances en création graphique si l’on utilise des Lottie fournis par les communautés de designers en ligne.

Nous avons également pu voir que les interactions avec les gestes utilisateur se font aisément.

Cependant, dès lors que nous avons besoin de détecter une interaction avec une zone bien précise dans le Lottie, nous sommes un peu plus limités, il est en effet difficile de capter un événement de clic, déplacement ou autre sur du contenu de l’animation.

De plus, avec Lottie il est possible de modifier des propriétés (couleurs, opacité, rotation, etc.) d’une forme précise au sein d’un Lottie. Ceci peut notamment être utile lorsque nous n’avons pas accès au fichier projet After Effects de l’animation ou si jamais certaines formes du Lottie doivent s’adapter au thème de l’application (couleurs par exemple). Dans ce cas, la structure des couches et formes de l’animation Lottie côté design est importante pour identifier convenablement les propriétés à modifier. Avec une structure complexe ou difficilement compréhensible, le travail du développeur risque d’être chronophage et complexe.

Pour conclure, Lottie est une solution open-source efficace et très légère qui pourra fournir une belle expérience visuelle à vos projets assez rapidement. Des alternatives telles que Rive permettent aussi d’intégrer des animations.

Vous souhaitez en savoir plus ? Contactez-nous !

Maxime BIZERAY – Développeur mobile
Samuel EBEBENGE – Développeur front-end
Pierre BOUCHER – Développeur fullstack
Majid SERHANE – Développeur mobile
Vincent LEBRAS – Développeur mobile
Anthony AUMOND – Développeur fullstack