Les transitions animées dans les listes et les tableaux avec VueJS11 min read

Designer’s Desk

VueJS fournit des moyens simples d’intégrer des transitions entre éléments. Nous nous penchons ici sur le cas particulier des listes et des tableaux.

Sans transitions, l’expérience utilisateur est médiocre

Dans cet article, nous allons travailler avec une liste de fruits présentée sous forme d’une liste et d’un tableau. En effet, la configuration des animations y est légèrement différente.

Ce code, qui nous servira de base, ne présente aucune transition. On comprend bien que l’utilisateur peut éprouver quelques difficultés à comprendre ce qui a changé à chaque modification des données. Je suis certain que les plus curieux d’entre-vous se sont déjà jetés sur l’onglet Vue pour y voir plus clair. L’importance des animations de transition est donc évidente.

Déplacement des éléments dans une liste

L’animation que je trouve la plus spectaculaire est celle des déplacements d’éléments à l’intérieur d’une liste ou d’un tableau. Et paradoxalement c’est la plus simple à mettre en place. Dans un simple tableau de présentation de données, elle peut même être suffisante à elle seule, pour animer les tris par colonnes par exemple.

VueJS met à notre disposition un composant nommé transition-group. Ce composant est spécialement conçu pour animer des listes d’éléments. Il contient donc à coup sûr un v-for.

Transitions dans une liste

Le composant transition-group possède un attribut tag. Cet attribut permet de renseigner le type de balise qui sera réellement rendu dans le DOM. En effet la balise transition-group n’existe pas en HTML. Elle est particulière à VueJS.

Dans notre exemple de liste, le composant transition-group prend la place de la balise ul. Nous donnons donc la valeur ul à l’attribut tag.

Le composant transition-group possède également un attribut name. La valeur de cet attribut sera utilisée comme racine pour définir les animations dans le CSS.

<ul class="list-group">
  <li class="list-group-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</ul>

Devient donc :

<transition-group class="list-group" name="fruit-list" tag="ul">
  <li class="list-group-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</transition-group>

A ce stade l’animation de déplacement des éléments de la liste est fonctionnelle, mais nous n’avons pas encore spécifié de durée pour cette animation. C’est donc sans effet visible. La durée se définit dans le CSS. Et c’est là que l’attribut name du transition-group nous est utile. VueJS donnera aux éléments en mouvement, pendant l’animation, la classe <name>-move, où <name> est la valeur de l’attribut name.

Ici par exemple on souhaite que l’animation de mouvement prenne une seconde :

.fruit-list-move {
  transition: transform 1s;
}

Transition dans un tableau

Pour la table, c’est la balise tbody, en théorie, que nous devrions remplacer par la balise transition-group. Cependant dans ce cas l’animation ne fonctionnera pas. Cela vient de l’échec de l’analyse du template de DOM qui doit nécessairement trouver une balise tbody dans une balise table.

Donc il est nécessaire de binder un transition-group dynamiquement sur le tbody avec l’attribut spécial is. Keskecéksecharabia ? Rien de bien compliqué, voyez par vous même :

<table class="table">
  [...]
  <tbody>
    <tr v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

Devient :

<table class="table">
  [...]
  <tbody name="fruit-table" is="transition-group">
    <tr v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

Et comme pour la liste, il nous faut ajouter une règle dans notre CSS :

.fruit-table-move {
  transition: transform 1s;
}

Résultat VueJS

Et voilà ce que ça donne :

Plutôt cool, hein ! Même l’ajout d’un élément est animé, si on fait l’impasse sur le “saut” vers le bas de la balise suivante (Number of items)… Et c’est ce que nous allons résoudre dans le prochain chapitre.

Ajout d’un élément dans une liste

Pour corriger le “saut” dont on parlait précédemment, voyons ce qui se passe actuellement :

  1. L’élément “Kiwis” apparaît. Il est en fait présent entièrement, juste caché derrière les éléments suivants.
  2. Les éléments suivants sont déplacés plus bas. Ils sont donc en mouvement et prennent une seconde pour se déplacer à leur nouvel emplacement.

On a donc la sensation que l’élément “Kiwis” apparaît progressivement alors qu’il arrive bien brutalement et est juste “dé-caché” progressivement par les éléments suivants de la liste qui rejoignent leur place. C’est bien cette apparition soudaine qui est trahie par le “saut” de la balise se trouvant plus bas.

Pour corriger ce comportement, on va donc jouer sur la hauteur de l’élément ajouté à la liste. En effet, nous allons faire varier sa hauteur avec une animation.

Transition d’entrée

VueJS applique automatiquement des classes aux éléments qui apparaissent ou qui disparaissent du DOM. Ces classes sont appliquées dynamiquement à différentes étapes de la transition d’entrée :

  • La classe v-enter est appliquée brièvement avant que l’élément soit inséré. Elle correspond à l’état de départ.
  • La classe v-enter-to est appliquée dès que l’élément est inséré, dès que v-enter est supprimée. Elle correspond à l’état cible de la fin de la transition, l’état dans lequel on veut que l’élément soit une fois la transition terminée.
  • La classe v-enter-active est appliquée tout au long de la transition. Elle est appliquée en même temps que v-enter et supprimée en même temps que v-enter-to.
Classes de gestion des transitions VueJS

Jongler avec ces 3 classes va nous permettre de paramétrer l’animation d’entrée d’un élément dans une liste ou un tableau.

Apparition d’un élément dans une liste

On renomme nos classes

En réalité, ce ne sont pas les classes v-enter, v-enter-to, et v-enter-active qui sont appliquées à notre élément, mais des classes qui ont pour suffixe ces noms. En effet, le nom des classes de transition est composé du nom donné à l’élément transition-group, suivi de l’un de ces noms-là.

Ainsi pour notre liste, les classes attribuées automatiquement par VueJS seront fruit-list-enter, fruit-list-enter-to, et fruit-list-enter-active.

Par rapport aux transitions de déplacement du chapitre précédent, nous devons également faire quelques modifications. En effet, nos transitions ne concernent plus uniquement les mouvements, mais également les apparitions, et les disparitions que nous verrons dans le prochain chapitre.

Nous allons donc remplacer :

.fruit-list-move {
  transition: transform 1s;
}

Par :

.fruit-list-item {
  transition: all 1s;
}

Et ajouter la classe fruit-list-item aux éléments de notre liste :

<transition-group class="list-group" name="fruit-list" tag="ul">
  <li class="list-group-item fruit-list-item" v-for="item in items" :key="item.id">
    [...]
  </li>
</transition-group>

Ainsi toutes les transitions appliquées individuellement aux éléments de la liste dureront une seconde. On garde donc le comportement des déplacements. Et les transitions d’apparition et de disparition dureront également une seconde.

Les subtilités du CSS

Renseignons maintenant les états d’entrée et de sortie de notre transition d’apparition dans la liste. Nous souhaitons que l’élément ait une hauteur nulle au départ pour atteindre sa hauteur définitive à la fin de la transition.

Or nous ne pouvons pas connaître la hauteur d’une div à l’avance. Elle est déterminée par la hauteur des éléments qu’elle contient. En revanche on peut définir une hauteur maximale qu’on ne veut pas qu’elle dépasse, grâce à la propriété max-height. Il nous suffit donc, pour l’état d’arrivée, de définir une hauteur maximale qu’on sait être toujours supérieure à la hauteur réelle. Il faut néanmoins définir cet objectif assez proche de la valeur réelle, car la vitesse d’animation sera calculée à partir de cet objectif et pourrait être trop rapide si la valeur cible était trop loin de la réalité.

Au début de l’animation, nous voulons une hauteur de 0. On défini donc l’attribut max-height à 0, mais cela ne suffit pas. En effet, notre div contient du padding. Ses attributs padding-top et padding-bottom ne sont pas nuls par défaut. Et ce sont eux qui empêchent notre div de disparaître complètement. La solution est simple, il suffit de donner également à ces deux attributs une valeur nulle pour le début de l’animation (.fruit-list-enter). Il n’est pas nécessaire d’en définir pour la fin de l’animation (.fruit-list-enter-to), puisque dès que la classe .fruit-list-enter sera supprimée, le padding reprendra ses valeurs par défaut comme cible.

Voici donc l’intégralité du code CSS à écrire pour l’apparition d’un élément dans une liste :

.fruit-list-item {
transition: all 1s;
}
.fruit-list-enter {
max-height: 0px !important;
padding-top: 0px !important;
padding-bottom: 0px !important;
}
.fruit-list-enter-to {
max-height: 80px;
}

Notez la présence de !important en bout de ligne pour nous assurer que cette consigne prendra le pas sur les autres qui s’y superposeraient.

Apparition d’une ligne dans un tableau

Le même principe s’applique aux tableaux, à un détail près : on ne peut pas forcer la hauteur d’une ligne de tableau en utilisant l’attribut max-height de celle-ci.

Comme pour les listes, commençons par ajouter une classe à nos lignes de tableau. Ainsi :

.fruit-table-move {
  transition: transform 1s;
}

Devient :

.fruit-table-item {
  transition: all 1s;
}

Et on modifie la balise tr :

<table class="table mb-0">
  [...]
  <tbody name="fruit-table" is="transition-group">
    <tr class="fruit-table-item" v-for="item in items" :key="item.id">
      [...]
    </tr>
  </tbody>
</table>

Vous l’avez compris, le CSS pour les transitions dans un tableau va être différent de celui pour les transitions dans une liste. En effet, la hauteur d’une balise tr dépend, comme une div, de son contenu, mais contrairement à une div, elle n’a pas un pouvoir total dessus. On va donc devoir agir également sur le contenu des lignes du tableau :

.fruit-table-item {
  transition: all 1s;
}
.fruit-table-item > * {
  transition: all 1s;
  overflow: hidden;
}
.fruit-table-enter {
  line-height: 0 !important;
}
.fruit-table-enter > * {
  padding-top: 0px !important;
  padding-bottom: 0px !important;
}

La hauteur des lignes de tableau se configure avec l’attribut line-height.

Le sélecteur CSS > *, présent deux fois dans cet extrait de code, nous permet de sélectionner tous les enfants de la balise tr (toutes les balises qu’elle contient). Nous l’utilisons pour paramétrer un padding haut et bas nul à nos lignes de texte.

Et voilà le résultat :

Notre exemple est relativement simple puisque les lignes du tableau ne contiennent que du texte. Lorsqu’elles contiennent des boutons ou des images par exemple, gérer les transitions dans un tableau avec VueJS devient un peu plus pénible.

Suppression d’un élément dans une liste

Nous arrivons à la dernière étape du sujet de cet article : l’animation de la disparition d’un élément dans une liste ou un tableau.

Comme pour les apparitions, VueJS applique automatiquement des classes aux éléments qui disparaissent du DOM. Ces classes sont appliquées dynamiquement à différentes étapes de la transition de sortie :

  • La classe v-leave est appliquée brièvement (une image) avant que l’élément ne commence la transition de sortie. Elle correspond à l’état de départ.
  • La classe v-leave-to est appliquée dès que l’élément commence à partir, dès que v-leave est supprimée. Elle correspond à l’état cible de la fin de la transition, l’état dans lequel on veut que l’élément soit une fois la transition terminée.
  • La classe v-leave-active est appliquée tout au long de la transition. Elle est appliquée en même temps que v-leave et supprimée en même temps que v-leave-to.
Transitions de sortie classes vueJS

Disparition d’un élément dans une liste ou un tableau

Il n’y a ici rien de magique. Comme nous réalisons la transition exactement inverse de l’apparition, un écrasement de la ligne jusqu’à sa disparition, le paramétrage est exactement le même. Il suffit d’ajouter les bonnes classes au CSS :

.fruit-list-item {
  transition: all 1s;
}
.fruit-list-enter,
.fruit-list-leave-to {
  max-height: 0px;
  padding-top: 0px !important;
  padding-bottom: 0px !important;
  overflow: hidden;
}
.fruit-list-enter-to,
.fruit-list-leave {
  max-height: 80px;
}

.fruit-table-item {
  transition: all 1s;
}
.fruit-table-item > * {
  transition: all 1s;
  overflow: hidden;
}
.fruit-table-enter,
.fruit-table-leave-to {
  line-height: 0;
}
.fruit-table-enter > *,
.fruit-table-leave-to > * {
  padding-top: 0px !important;
  padding-bottom: 0px !important;
}

Et le résultat final :

Voilà ! Vous savez tout.

Pour aller plus loin

Comme je n’invente rien, je vous donne mes sources. Ou dans ce cas, ma source, la documentation VueJS, à la page des transitions : https://fr.vuejs.org/v2/guide/transitions.html

N’hésitez pas à jouer avec les JSFiddle donnés dans cet article ou à en créer vous-même. C’est le meilleur moyen de comprendre et d’apprendre.

Leave a Reply