frontend / vuejs
Transitions & animations
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
*-movepour 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.
| Classe | Moment |
|---|---|
*-enter-from | État de départ, juste avant l'apparition |
*-enter-active | Pendant 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-active | Pendant 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.
<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><!-- 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><!-- 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é parv-if/v-show,<TransitionGroup>pour une liste.- Les 6 classes clés :
*-enter-from/active/toet*-leave-from/active/to. - La classe
*-movedeTransitionGroupanime le déplacement des éléments qui restent en place. :css="false"+ hooks JS (@enter,@leaveavecdone()) pour déléguer l'animation à une librairie externe.
Exercices pratiques
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.