Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Перехід та transitiongroup у Vue.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Vue Transition** - вбудований компонент, який обгортає один елемент і застосовує CSS-класи анімації, коли елемент з'являється або зникає з DOM. `<TransitionGroup>` робить те ж саме для списків і додає анімацію переміщення (move transition) при зміні порядку. ```vue <Transition name="fade"> <p v-if="show">Привіт!</p> </Transition> ``` **Ключове:** Vue додає `fade-enter-from`, `fade-enter-active` та `fade-enter-to` при вході; аналогічні `leave`-класи при виході. Для перемикання компонентів використовуй `mode="out-in"`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Vue Transition** - вбудований компонент, який обгортає один елемент і додає CSS-класи в потрібний момент, щоб анімації входу та виходу працювали без жодних зовнішніх бібліотек. ## Теорія ### TL;DR - `<Transition>` анімує один елемент; `<TransitionGroup>` анімує список `v-for` - Vue автоматично додає шість CSS-класів: `v-enter-from`, `v-enter-active`, `v-enter-to` при вході та такі ж `leave`-варіанти при виході - Встанови `name="fade"` і префікс `v-` стане `fade-` - `mode="out-in"` запобігає накладанню двох компонентів під час перемикання - JavaScript-хуки (`@enter`, `@leave`) дозволяють підключити GSAP або будь-яку іншу бібліотеку, коли CSS недостатньо ### Швидкий приклад ```vue <template> <button @click="show = !show">Перемкнути</button> <Transition name="fade"> <p v-if="show">Привіт!</p> </Transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; /* приховано на початку входу і в кінці виходу */ } </style> ``` Vue додає `.fade-enter-from` і `.fade-enter-active` в той кадр, коли `<p>` потрапляє в DOM. Через один кадр додається `.fade-enter-to` і прибирається `enter-from`. Коли перехід завершується, всі класи видаляються. При виході все відбувається у зворотньому порядку. ### Потік CSS-класів Шість класів йдуть у фіксованій послідовності. Префікс за замовчуванням `v-`, або те, що передаєш у `name`: ``` ENTER: v-enter-from → v-enter-active → v-enter-to LEAVE: v-leave-from → v-leave-active → v-leave-to ``` | Клас | Коли застосовується | |---|---| | `fade-enter-from` | Перший кадр входу (початковий стан) | | `fade-enter-active` | Весь час входу | | `fade-enter-to` | Останній кадр входу (кінцевий стан) | | `fade-leave-from` | Перший кадр виходу (початковий стан) | | `fade-leave-active` | Весь час виходу | | `fade-leave-to` | Останній кадр виходу (кінцевий стан) | У клас `-active` ти пишеш `transition` або `animation`. У `-from` і `-to` описуєш початковий і кінцевий стани. Такий поділ дозволяє вільно комбінувати CSS-переходи та CSS-анімації. ### TransitionGroup для списків `<TransitionGroup>` додає те, чого `<Transition>` не вміє: анімацію переміщення (move transition), коли елементи списку змінюють порядок. Vue використовує техніку FLIP під капотом: фіксує позиції до і після зміни DOM, потім анімує різницю через `transform`. ```vue <TransitionGroup name="list" tag="ul"> <li v-for="item in items" :key="item.id"> {{ item.text }} </li> </TransitionGroup> <style> .list-enter-active, .list-leave-active { transition: all 0.3s ease; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); } .list-move { transition: transform 0.3s ease; /* анімація перерозташування */ } .list-leave-active { position: absolute; /* виводимо елемент з потоку */ } </style> ``` Клас `.list-move` тут головний. Без нього елементи просто стрибають на нові місця при видаленні сусіда. А `position: absolute` для `.list-leave-active` потрібен тому, що елемент, який виходить, ще займає місце в макеті під час анімації. Без цього решта елементів не зможе плавно заповнити звільнений простір. ### Режими переходу За замовчуванням `<Transition>` запускає вхід і вихід одночасно. При перемиканні компонентів це зазвичай призводить до накладання. `mode="out-in"` це виправляє. ```vue <!-- поточний компонент повністю виходить, потім входить новий --> <Transition name="fade" mode="out-in"> <component :is="currentView" :key="currentView" /> </Transition> ``` Завжди додавай `:key` на компонент всередині `<Transition>`. Без нього Vue може повторно використати той самий DOM-вузол і анімація взагалі не спрацює. `mode="in-out"` робить навпаки: новий компонент входить, поки старий ще видимий, потім старий виходить. Зустрічається рідше, але підходить для ефектів накладання шарів. ### JavaScript-хуки CSS вирішує більшість завдань. Коли потрібен таймлайн, фізика пружини або SVG-морфінг, використовуй хуки і передавай управління бібліотеці: ```vue <Transition @before-enter="onBeforeEnter" @enter="onEnter" @leave="onLeave" :css="false" > <div v-if="show">Контент</div> </Transition> <script setup> import gsap from 'gsap' function onBeforeEnter(el) { gsap.set(el, { opacity: 0, y: -20 }) } function onEnter(el, done) { gsap.to(el, { opacity: 1, y: 0, duration: 0.4, onComplete: done }) } function onLeave(el, done) { gsap.to(el, { opacity: 0, y: 20, duration: 0.3, onComplete: done }) } </script> ``` Встановлюй `:css="false"`, коли JS-хуки виконують всю роботу. Це вимикає ін'єкцію класів у Vue. Якщо пропустити, Vue може викликати `done` зарано і конфліктувати з таймінгом бібліотеки. ### Типові помилки **Відсутній `:key` на динамічних компонентах** ```vue <!-- Vue повторно використовує DOM-вузол - анімація не спрацьовує --> <Transition name="fade" mode="out-in"> <component :is="currentView" /> </Transition> <!-- правильно --> <Transition name="fade" mode="out-in"> <component :is="currentView" :key="currentView" /> </Transition> ``` **Забули `position: absolute` для `.list-leave-active`** Без цього елемент, що виходить, зберігає місце в макеті під час анімації. Решта елементів не рухається, поки Vue його не видалить, тому анімація перерозташування виглядає зламаною. Це найпоширеніша проблема з `<TransitionGroup>`, яку я бачу на code review. **Не викликається `done()` у JS-хуках** Хуки `@enter` і `@leave` отримують колбек `done`. Якщо його не викликати, Vue вважає, що перехід все ще триває. Елемент залишається в стані переходу безкінечно, і жодні наступні анімації на ньому не спрацюють. **Кілька елементів всередині `<Transition>`** `<Transition>` очікує рівно один дочірній елемент. Два сусідніх елементи всередині викличуть попередження в runtime. Загорни їх в один div або переходь до `<TransitionGroup>`. ### Де зустрічається - Модальні вікна: `<Transition name="modal" mode="out-in">` для оверлея - Вкладки: `mode="out-in"` на `<component :is="activeTab">` - Сповіщення-тости: `<TransitionGroup>` зі слайдом зверху - Переходи між сторінками у Vue Router: обгортаємо `<RouterView>` в `<Transition>` - Покрокові анімації списку: хук `@enter` з `el.dataset.index` для затримки кожного елемента ### Питання на співбесіді **Q:** Яка різниця між `<Transition>` і `<TransitionGroup>`? **A:** `<Transition>` анімує один елемент при вході або виході. `<TransitionGroup>` анімує список і додатково обробляє переміщення елементів при зміні порядку, використовуючи FLIP під капотом. **Q:** Навіщо існує `mode="out-in"`? **A:** Без нього вхід і вихід відбуваються одночасно. При перемиканні видів це спричиняє накладання двох панелей. `out-in` спочатку завершує вихід, потім починає вхід. **Q:** Що станеться, якщо не викликати `done()` у JS-хуці? **A:** Vue вважатиме перехід незавершеним. Елемент залишиться в стані переходу безкінечно, і жодні наступні анімації на ньому не запустяться. **Q:** Як `<TransitionGroup>` анімує переміщення елементів? **A:** Використовує FLIP: фіксує позиції до і після зміни DOM, застосовує інвертований transform, щоб елементи здавалися на старому місці, потім переходить до нульового значення. Клас `.list-move` керує тривалістю цього переходу. **Q:** Коли використовувати `:css="false"`? **A:** Коли JS-хуки роблять всю анімаційну роботу. Це не дає Vue додавати класи переходу, які можуть конфліктувати з таймінгом зовнішньої бібліотеки. ## Приклади ### Базове появлення та зникнення ```vue <template> <button @click="show = !show">Показати повідомлення</button> <Transition name="fade"> <p v-if="show" class="notice">Збережено успішно.</p> </Transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.25s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style> ``` Два CSS-правила, без JavaScript. Повідомлення з'являється при `show = true` і зникає при `show = false`. ### Перемикання видів з mode="out-in" ```vue <template> <nav> <button @click="view = 'HomeView'">Головна</button> <button @click="view = 'ProfileView'">Профіль</button> </nav> <Transition name="slide" mode="out-in"> <component :is="view" :key="view" /> </Transition> </template> <style> .slide-enter-active, .slide-leave-active { transition: transform 0.3s ease, opacity 0.3s ease; } .slide-enter-from { transform: translateX(20px); opacity: 0; } .slide-leave-to { transform: translateX(-20px); opacity: 0; } </style> ``` `mode="out-in"` змушує поточний вид слайдитися вліво і зникати, перш ніж наступний з'явиться справа. Без `mode` обидва анімуються одночасно і перекриваються. ### Список із сортуванням та анімацією переміщення ```vue <template> <button @click="shuffle">Перемішати</button> <TransitionGroup name="cards" tag="ul" class="card-list"> <li v-for="card in cards" :key="card.id" class="card"> {{ card.label }} </li> </TransitionGroup> </template> <script setup> import { ref } from 'vue' const cards = ref([ { id: 1, label: 'Картка A' }, { id: 2, label: 'Картка B' }, { id: 3, label: 'Картка C' }, ]) function shuffle() { cards.value = [...cards.value].sort(() => Math.random() - 0.5) } </script> <style> .card-list { position: relative; /* потрібен для абсолютного позиціонування карток при виході */ list-style: none; padding: 0; } .cards-move { transition: transform 0.4s ease; } .cards-enter-active, .cards-leave-active { transition: all 0.3s ease; } .cards-enter-from, .cards-leave-to { opacity: 0; transform: scale(0.8); } .cards-leave-active { position: absolute; } </style> ``` Кожне перемішування запускає FLIP для кожної картки, що змінила позицію. Клас `.cards-move` керує плавним переміщенням. `position: relative` на батьківському елементі потрібен, щоб картки, що виходять, залишались на місці візуально під час анімації виходу.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.