Commentaires
Les transitions avec Vue.JS ont la même utilité qu'en CSS. D'ailleurs, on peut très bien utiliser appliquer des transitions en CSS sur des éléments gérés par Vue.JS (dans notre container #app) sans problème. On peut également appliquer des animations pour des effets plus poussés en jouant avec le style conditionnel. Un exemple sur codepen [animation codepen].

Mais Vue.JS nous permet d'appliquer des transitions via des composants directement dans le code et d'agir sur plusieurs moments de cette transition. Ainsi, on peut appliquer une transition sur un ensemble de balises du DOM qui s'activera dès qu'un ajout, une modification un une suppression d'un élément s'effectuera.

Arthur, l'apprenti développeurDes composants ? On peut utiliser des composants dans le DOM ? Comment ça ? Et qu'est-ce que c'est ?
Malheureusement pour te répondre Arthur, il va falloir attendre que nous finissions ce cours et que l'on réalise le prochain pour que tu saches pleinement ce que sont les composants et comment les utiliser. Les composants sont au coeur de Vue.JS mais nous avons besoins de bases avant !

Arthur, l'apprenti développeurBon d'accord, mais ça ne m'empêchera pas de comprendre cette partie ?
Non aucun problème ! Je vais rester très léger sur cette partie, si tu veux en savoir plus n'hésite pas à regarder la documentation officielle de Vue.

Les transitions d'entrée et de sortie


Ces transitions s'exécutent quand un élément apparait ou disparait (utilisation de v-if, v-else, v-else-if ou v-show). Elles s'exécutent aussi en cas de composants dynamiques, mais on verra ça bien plus tard.
Comme je l'ai dit précédemment, il faut utiliser un composant qui englobe le contenu sur lequel on veut appliquer la transition. Un composant n'est ni plus moins, dans le DOM, qu'une balise. Comme toute balise, on peut lui renseigner des attributs. Dans le cadre d'une transition, il faut lui attribuer un nom grâce à l'attribut name pour ensuite appliquer du style sur cette transition.
Je reprends un de nos anciens exemples :

{"language":"text/html","content":"<main id=\"app\" class=\"px-4 pt-8 w-full flex flex-wrap\">\n <button v-on:click=\"open = !open\">Afficher le contenu</button>\n <Transition name=\"fading\">\n <div v-show=\"open\">Je suis le contenu à afficher</div>\n </Transition>\n\n</main>\n<script type=\"module\">\n import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'\n createApp({\n data() {\n return {\n open: false,\n }\n }\n }).mount('#app')\n</script>","filename":"index.html"}

Arthur, l'apprenti développeurMais attends, il ne se passe rien de plus qu'avant dans ton exemple ?

C'est normal, on n'a pas stylisé notre transition.
Une transition peut se voir stylisée à 6 moments différents (un peu comme des hooks) qui sont nommés :

  • v-enter-from : cette phase est celle où un élément qui doit subir un changement (être ajouté, apparaitre) n'a pas encore eu lieu, c'est juste avant. Elle est supprimée une fois que l'élément a subi son changement.

  • v-enter-to : cette phase est celle où un élément qui doit subir un changement a eu lieu. Elle est déclenchée donc juste après v-enter-from. Elle est ensuite enlevée une fois que la transition est terminée.

  • v-enter-active : cette phase couvre toute la période de v-enter-from à v-enter-to. On utilise cette phase pour renseigner la durée, le délai et la courbe d'évolution de la transition

  • v-leave-from : pareil que v-enter-from mais quand l'étudiant disparait ou est supprimé

  • v-leave-to : pareil que v-leave-to mais quand l'étudiant disparait ou est supprimé

  • v-leave-active : pareil que v-leave-active mais quand l'étudiant disparait ou est supprimé



Ces hooks peuvent être utilisés dans le CSS pour styliser la transition en précisant dans le CSS ces noms de classe :
{"language":"text/html","content":"<style>\n\t.fading-enter-from{\n\n\t}\n\n\t.fading-enter-active{\n\n\t}\n\n\t.fading-enter-to{\n\n\t}\n\t.fading-leave-from{\n\n\t}\n\n\t.fading-leave-active{\n\n\t}\n\n\t.fading-leave-to{\n\n\t}\n</style>","filename":""}

Évidemment, j'ai appelé mes classes fading-xxxx car j'ai donné à ma transition le nom de fading dans mon exemple. Ce nom change donc à chaque fois qu'on donne un nom différent aux transitions.

Voici un exemple entier :
{"language":"text/html","content":"<!DOCTYPE html>\n<html lang=\"fr\">\n\n<head>\n <meta charset=\"utf-8\">\n <title>Mes premiers pas avec Vue 3</title>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <style>\n [v-cloak] {\n display: none;\n }\n\n .fading-enter-active,\n .fading-leave-active {\n transition: opacity 1s ease;\n }\n\n .fading-enter-to,\n .fading-leave-from {\n background: darkcyan;\n }\n\n .fading-enter-from,\n .fading-leave-to {\n opacity: 0;\n }\n </style>\n</head>\n\n<body>\n <main id=\"app\" class=\"px-4 pt-8 w-full flex flex-wrap\" v-cloak>\n <button v-on:click=\"open = !open\">Afficher le contenu</button>\n <Transition name=\"fading\">\n <div v-show=\"open\">Je suis le contenu à afficher</div>\n </Transition>\n\n </main>\n <script type=\"module\">\n import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'\n createApp({\n data() {\n return {\n open: false,\n }\n }\n }).mount('#app')\n </script>\n</body>\n\n</html>","filename":"index.html"}


Je te laisse l'essayer pour mieux comprendre et manipuler les transitions.

C'est compris ?

Arthur, l'apprenti développeurC'est compris !
Alors continuons. Il est également possible d'agir sur ces "hooks" directement dans Vue en tant que méthodes. Pour ça, on va ajouter des listeners sur chacun des hooks avec v-on.

{"language":"text/html","content":"<Transition\n v-on:before-enter=\"enterFrom\"\n v-on:enter=\"enterActive\"\n v-on:after-enter=\"enterTo\"\n v-on:enter-cancelled=\"enterCancelled\"\n v-on:before-leave=\"leaveFrom\"\n v-on:leave=\"leaveActive\"\n v-on:after-leave=\"leaveTo\"\n v-on:leave-cancelled=\"leaveCancelled\"\n>\n <!-- ... -->\n</Transition>","filename":"index.html"}

Et dans nos méthodes :

{"language":"text/javascript","content":"methods: {\n // Enter\n\n enterFrom(el) {\n // ...\n },\n // la fonction de callback done() est optionnelle quand on utilise du CSS en + du JS\n enterActive(el, done) {\n done()\n },\n\n enterTo(el) {\n // ...\n },\n enterCancelled(el) {\n /// ...\n }\n\n\n // Leave\n\n leaveFrom(el) {\n // ...\n },\n // Pareillement\n leaveActive(el, done) {\n // ...\n done()\n },\n leaveTo(el) {\n // ...\n },\n leaveCancelled(el) {\n\n },\n}","filename":"vue.js"}

On peut alors faire une action quand la transition est déclenchée et donc faire des effets plus puissants. D'ailleurs, on peut préciser à Vue d'utiliser uniquement nos hooks sur les transitions dans Vue plutôt que du CSS en précisant :css="false" sur <Transition>.

Je ne fournirai pas d'exemple sur cette partie car c'est vrai qu'en général, le CSS suffit.

Bien. Je n'ai couvert qu'une petite partie des possibilités, mais pour notre niveau ça suffit. On va maintenant voir un deuxième type de transition : les transitions de groupe

Transitions de groupe


Dans le cas où on voudrait appliquer une transition sur une liste, c'est à dire sur des éléments générés par v-for, on ne peut pas utiliser <Transition>. Il faut utiliser <TransitionGroup>, mais l'idée est strictement la même.

{"language":"text/html","content":"<!DOCTYPE html>\n<html lang=\"fr\">\n\n<head>\n <meta charset=\"utf-8\">\n <title>Mes premiers pas avec Vue 3</title>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <style>\n [v-cloak] {\n display: none;\n }\n\n span{\n\t\t\tdisplay:inline-block;\n\t\t\tpadding:0 1rem;\n\t\t}\n\n\t\t.blink-enter-active {\n\t \t\tanimation: blink 2s;\n\t\t}\n\t\t.blink-leave-active {\n\t\t \tanimation: blink 0.5s reverse;\n\t\t}\n\n\t\t@keyframes blink {\n\t \t0% {\n\t \topacity:0;\n\t \ttransform:scale(0.5);\n\t \tcolor:yellow;\n\t \t}\n\t \t50% {\n\t \topacity:0.7;\n\t \ttransform:scale(1.2);\n\t \tcolor:red;\n\t \t}\n\t \t100% {\n\t \ttransform: scale(1);\n\t \topacity:1;\n\t \tcolor:black;\n\t \t}\n\t}\n </style>\n</head>\n\n<body>\n <main id=\"app\" class=\"px-4 pt-8 w-full flex flex-wrap\" v-cloak>\n\t\t<button @click=\"add\">Ajouter un nombre</button>\n\t\t<TransitionGroup name=\"blink\">\n\t\t\t<span v-for=\"(number, key) in random\" :key=\"key\">\n\t\t\t\t{{ number }}\n\t\t\t</span>\n\t\t</TransitionGroup>\n\t</main>\n <script type=\"module\">\n import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'\n const randomNumber = (max) => {\n \t\t\treturn Math.floor(Math.random() * Math.floor(max));\n\t\t}\n createApp({\n data() {\n\t\t\t return {\n\t\t\t \trandom:[1, 8, 5, 2, 9]\n\t\t\t }\n\t\t\t},\n\t\t\tmethods:{\n\t\t\t\tadd() {\n\t\t\t\t\tthis.random.push(randomNumber(15))\n\t\t\t\t}\n\t\t\t}\n }).mount('#app')\n </script>\n</body>\n\n</html>","filename":"index.html"}


Arthur, l'apprenti développeurAh et tu peux aussi utiliser des animations avec les transitions Vue ?
C'est exact aussi, comme je viens de le faire. Tu peux laisser libre recours à ton imagination, les possibilités sont donc infinies !
Petite indication également : tu peux ajouter un attribut "tag" sur TransitionGroup si tu veux que ce composant soit utilisé comme une vraie balise et remplacer par une vraie balise.
{"language":"text/html","content":"<TransitionGroup name=\"blink\" tag=\"section\">\n\t<span v-for=\"(number, key) in random\" :key=\"key\">\n\t\t{{ number }}\n\t</span>\n</TransitionGroup>","filename":""}


Avec ce code, on verra apparaître une balise <section></section> qui englobera nos span ;)

Bon, reprenons notre mini-projet. On veut faire apparaitre la partie concernant notre sélection de manière progressive.
Je te laisse essayer, et tu trouveras ma proposition ci-après !

{"language":"text/html","content":"<!DOCTYPE html>\n<html lang=\"fr\">\n\n<head>\n <meta charset=\"utf-8\">\n <title>Mes premiers pas avec Vue 3</title>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <style>\n [v-cloak] {\n display: none;\n }\n\n .fade-enter-active,\n .fade-leave-active {\n transition: all 0.5s ease;\n }\n\n .fade-enter-from,\n .fade-leave-to {\n opacity: 0;\n }\n </style>\n</head>\n\n<body>\n <main id=\"app\" v-cloak>\n <Transition name=\"fade\">\n <div v-show=\"modal == 'open'\"\n class=\"w-1/2 h-1/2 bg-white px-2 py-2 z-10 overflow-y-auto shadow-md rounded fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\">\n <h2 class=\"text-xl py-4\">Mes articles en attente</h2>\n <a :href=\"`http://monsite.com/${post.id}`\" v-for=\"post in selection\" :key=\"post.id\" class=\"block\">{{\n post.title\n }}</a>\n </div>\n </Transition>\n <div v-if=\"loading\">Chargement en cours...</div>\n\n <p v-if=\"posts.length < 1\"\n class=\"max-w-7xl mx-auto border-l-4 border-yellow-400 bg-yellow-50 p-4 text-sm text-yellow-700\">\n Aucun article à afficher.\n </p>\n <div v-else class=\"max-w-7xl mx-auto grid gap-4 grid-cols-3\">\n <article v-for=\"post in posts\" class=\"shadow px-4 pb-8 pt-2 rounded relative\" :key=\"post.id\">\n <a :href=\"`http://monsite.com/${post.id}`\" class=\"mt-4 block\">\n <h2 class=\"text-xl font-semibold text-gray-900\">{{ post.title }}</h2>\n <p class=\"mt-3 text-base text-gray-500\">{{ post.body }}</p>\n </a>\n <button @click=\"toggleSelection(post, $event.target)\" class=\"text-sm absolute bottom-2 px-2 rounded\"\n :class=\"[selection.includes(post) ? css.ButtonRemove : css.ButtonAdd]\">\n Ajouter à ma liste\n </button>\n </article>\n </div>\n <footer v-if=\"selection.length > 0\" class=\"fixed bottom-0 right-2 px-2 py-4 rounded bg-gray-300\">\n <button @click=\"modal='open'\">Voir {{ selection.length > 1 ? 'les' : '' }} {{ selection.length }}\n article{{selection.length > 1 ? 's' : '' }} à lire plus tard</button>\n </footer>\n </main>\n\n <script type=\"module\">\n import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'\n createApp({\n data() {\n return {\n loading: true,\n posts: [],\n selection: [],\n css: {\n ButtonAdd: 'text-green-700 bg-green-200 hover:bg-green-300',\n ButtonRemove: ['text-yellow-700 bg-yellow-100 hover:bg-yellow-200'],\n },\n modal: 'close',\n }\n },\n methods: {\n toggleSelection(post, button) {\n if (this.selection.includes(post)) {\n this.selection.splice(this.selection.indexOf(post), 1)\n button.textContent = 'Ajouter à ma liste'\n } else {\n this.selection.push(post)\n button.textContent = 'Enlever de ma liste'\n }\n }\n },\n beforeCreate() {\n this.loading = true;\n },\n created() {\n fetch('https://jsonplaceholder.typicode.com/posts')\n .then((response) => response.json())\n .then((json) => { this.posts = json });\n },\n mounted() {\n this.loading = false\n }\n }).mount('#app')\n\n </script>\n</body>\n\n</html>","filename":"index.html"}

Arthur, l'apprenti développeurJ'aime bien cette transition ! La mienne était moins belle...
Pas grave, l'important c'est d'essayer et de pratiquer !

Bon maintenant qu'on a fait tout ça, on va ajouter quelques fonctionnalités à notre projet : on va créer un formulaire permettant à une personne d'ajouter un article. Évidemment, ce ne sera que côté front, donc encore une fois, pas de persistance des données, mais l'idée est là ! J'ai terminé cette partie
Demander de l'assistance