Retour au cours

frontend / vuejs

Transitions & animations

Leçon 171 exercice

Explication

Ce que vous allez apprendre

  • Comprendre les six classes CSS que <Transition> applique automatiquement à l'entrée et à la sortie
  • Animer une liste entière (ajout, suppression, réordonnancement) avec <TransitionGroup>
  • Utiliser la classe *-move pour animer le déplacement des éléments qui restent en place
  • Piloter une animation via l'API JavaScript plutôt que via CSS pour des cas plus complexes
  • Choisir entre <Transition> (un seul élément) et <TransitionGroup> (une liste)

Dans quel contexte ?

Une liste de tâches doit s'animer proprement à chaque ajout, suppression ou réorganisation : les nouvelles tâches apparaissent en fondu, les tâches supprimées disparaissent progressivement, et les tâches restantes glissent doucement vers leur nouvelle position plutôt que de sauter instantanément. Sans les composants de transition de Vue, il faudrait orchestrer manuellement ces animations avec des classes CSS ajoutées et retirées au bon moment — un travail fastidieux et source de bugs.

D'abord, six classes appliquées automatiquement aux bons moments

<Transition name="fondu"> génère automatiquement six classes CSS aux instants clés de l'entrée et de la sortie d'un élément conditionné par v-if/v-show. Il suffit de définir le style correspondant à chacune pour obtenir une animation fluide, sans jamais manipuler ces classes manuellement en JavaScript.

ClasseMoment
*-enter-fromÉtat de départ, juste avant l'apparition
*-enter-activePendant toute la durée de l'entrée
*-enter-to (implicite)État final de l'entrée
*-leave-from (implicite)État de départ de la sortie
*-leave-activePendant toute la durée de la sortie
*-leave-toÉtat final, juste avant la disparition

Prérequis

Il faut avoir compris v-if/v-show (leçon 2) : <Transition> s'applique directement à un élément conditionné par l'une de ces deux directives.

Ensuite, animer une liste entière plutôt qu'un seul élément

<TransitionGroup> étend ce principe à une liste complète rendue avec v-for, en animant l'ajout, la suppression, ET le réordonnancement des éléments. Contrairement à <Transition>, il rend TOUJOURS un élément conteneur réel dans le DOM (tag="ul" par exemple), qu'il faut spécifier explicitement.

Piège fréquent

Sans position: absolute sur la classe *-leave-active, les éléments voisins d'un élément en cours de suppression ne peuvent pas glisser vers leur nouvelle position PENDANT que cet élément disparaît encore — l'animation semble alors saccadée, avec un "trou" qui apparaît brutalement plutôt que de se refermer progressivement.

Il reste une classe spéciale, propre à <TransitionGroup>

La classe *-move anime spécifiquement le déplacement des éléments qui restent affichés mais changent de position (après un tri, par exemple). Sans elle, les éléments existants "sauteraient" instantanément à leur nouvelle position au lieu de glisser progressivement.

Astuce

Pour une animation complexe qu'une simple transition CSS ne peut pas exprimer (rebond physique, séquençage précis), les hooks JavaScript (@enter, @leave avec :css="false") permettent de déléguer entièrement l'animation à l'API Web Animations native ou à une bibliothèque comme GSAP. Le paramètre done DOIT être appelé pour signaler à Vue que la transition est terminée, sinon l'élément resterait bloqué dans un état intermédiaire.

Maintenant que l'interface est à la fois réactive et agréable visuellement, la prochaine leçon aborde un sujet tout aussi essentiel avant la mise en production : comment tester ces composants de façon fiable et automatisée.

Commandes & code

Transitions & animations

Le composant <Transition> applique automatiquement des classes CSS aux moments clés de l'entrée/sortie du DOM.

vue
<script setup>
import { ref } from 'vue'
const visible = ref(true)
</script>

<template>
  <button @click="visible = !visible">Basculer</button>

  <!-- name="fondu" génère les classes fondu-enter-active, fondu-leave-active, etc. -->
  <Transition name="fondu">
    <p v-if="visible">Ce paragraphe apparaît/disparaît en fondu</p>
  </Transition>
</template>

<style scoped>
/* *-enter-active / *-leave-active : durée et courbe d'animation, appliquées pendant TOUTE la transition */
.fondu-enter-active,
.fondu-leave-active {
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* *-enter-from : état de départ à l'entrée (juste avant que l'élément apparaisse) */
.fondu-enter-from {
  opacity: 0;
  transform: translateY(-8px);
}

/* *-leave-to : état final à la sortie (juste avant que l'élément disparaisse) */
.fondu-leave-to {
  opacity: 0;
  transform: translateY(8px);
}
</style>
vue
<!-- TransitionGroup : anime une LISTE (ajout, suppression, réordonnancement) -->
<script setup>
import { ref } from 'vue'
const taches = ref([
  { id: 1, texte: 'Réviser Vue' },
  { id: 2, texte: 'Écrire les tests' },
])
let prochainId = 3

function ajouter() {
  taches.value.push({ id: prochainId++, texte: `Tâche ${prochainId}` })
}
function retirer(id) {
  taches.value = taches.value.filter((t) => t.id !== id)
}
function melanger() {
  taches.value = [...taches.value].sort(() => Math.random() - 0.5)
}
</script>

<template>
  <button @click="ajouter">Ajouter</button>
  <button @click="melanger">Mélanger</button>

  <!-- tag="ul" : TransitionGroup ne rend PAS de conteneur par défaut, on précise l'élément voulu -->
  <TransitionGroup tag="ul" name="liste" class="liste-taches">
    <li v-for="t in taches" :key="t.id" @click="retirer(t.id)">
      {{ t.texte }}
    </li>
  </TransitionGroup>
</template>

<style scoped>
.liste-enter-active,
.liste-leave-active {
  transition: all 0.4s ease;
}
.liste-enter-from,
.liste-leave-to {
  opacity: 0;
  transform: scale(0.8);
}
/* leave-active en position absolue : nécessaire pour que les voisins glissent PENDANT la sortie */
.liste-leave-active {
  position: absolute;
}
/* move : anime le réordonnancement des éléments restants (ex: après un mélange) */
.liste-move {
  transition: transform 0.4s ease;
}
</style>
vue
<!-- Hooks JS de transition : pour piloter une lib d'animation (GSAP, anime.js...) -->
<script setup>
function surAvantEntree(el) {
  el.style.opacity = 0
}
function surEntree(el, done) {
  // animation JS custom, `done` DOIT être appelé pour signaler la fin de la transition
  const anim = el.animate([{ opacity: 0, transform: 'scale(0.9)' }, { opacity: 1, transform: 'scale(1)' }], {
    duration: 300,
    easing: 'ease-out',
  })
  anim.onfinish = done
}
</script>

<template>
  <Transition @before-enter="surAvantEntree" @enter="surEntree" :css="false">
    <div v-if="true">Contenu animé via l'API Web Animations</div>
  </Transition>
</template>

Résumé

  • <Transition> pour un élément unique conditionné par v-if/v-show, <TransitionGroup> pour une liste.
  • Les 6 classes clés : *-enter-from/active/to et *-leave-from/active/to.
  • La classe *-move de TransitionGroup anime le déplacement des éléments qui restent en place.
  • :css="false" + hooks JS (@enter, @leave avec done()) pour déléguer l'animation à une librairie externe.

Exercices pratiques

1 disponible
1

Mission : une liste de tâches qui saute au lieu de glisser

Objectif : Diagnostiquer une TransitionGroup mal configurée qui casse l'animation de réordonnancement, puis la corriger complètement.

Contexte

La liste de tâches de cette leçon anime correctement l'ajout et la suppression d'une tâche, mais un développeur a retiré par erreur la règle CSS .liste-move en pensant qu'elle était inutile, et a aussi oublié position: absolute sur .liste-leave-active. Résultat : cliquer sur "Mélanger" fait sauter instantanément toutes les tâches à leur nouvelle position, et supprimer une tâche au milieu de la liste fait "sauter" brutalement les tâches suivantes vers le haut au lieu de glisser.

Résoudre l’exercice →