Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Анимации в Vue 3
vue_animations

Анимации в Vue 3

Компоненты Transition и TransitionGroup, CSS-классы анимаций, JavaScript-хуки, GSAP и анимация списков

Переходы и анимации в Vue 3

Vue предоставляет встроенную систему переходов, которая интегрирована с реактивностью и маршрутизацией. Правильно реализованные анимации улучшают UX — они дают пользователю понимание того, что происходит в интерфейсе.

#CSS transitions vs JS animations: когда что выбирать

CSS переходы — первый выбор в большинстве случаев. Браузер может оптимизировать их через GPU, они не блокируют главный поток, просты в написании и поддержке.

JavaScript анимации выбирайте когда:

  • Нужен контроль над каждым кадром (requestAnimationFrame)
  • Анимация зависит от динамических значений (скорость, рассчитанная в рантайме)
  • Нужны сложные последовательности и таймлайны (GSAP)
  • CSS ограничивает возможности (анимация по кривой Безье с произвольными точками)

#Компонент <Transition>: базовое использование

<Transition> оборачивает одиночный элемент или компонент и добавляет CSS-классы в нужные моменты жизненного цикла перехода:

<template> <button @click="show = !show">Переключить</button> <!-- name задаёт префикс CSS-классов --> <Transition name="fade"> <p v-if="show" class="message">Привет! Я появляюсь с анимацией</p> </Transition> </template> <script setup lang="ts"> import { ref } from 'vue' const show = ref(false) </script> <style scoped> /* Появление */ .fade-enter-from { opacity: 0; } .fade-enter-active { transition: opacity 0.3s ease; } .fade-enter-to { opacity: 1; } /* Исчезновение */ .fade-leave-from { opacity: 1; } .fade-leave-active { transition: opacity 0.3s ease; } .fade-leave-to { opacity: 0; } </style>

Шесть CSS-классов управляют всем переходом. Суффикс -active определяет саму анимацию (transition/animation), суффиксы -from/-to задают начальное и конечное состояние.

Типичная ошибка: писать opacity в -active вместо -from/-to. Класс -active присутствует на протяжении всего перехода, он задаёт timing-функцию, не значения.

#Режимы mode="out-in" и "in-out"

По умолчанию входящий и исходящий элементы анимируются одновременно. Это часто выглядит как наложение. mode задаёт порядок:

<template> <!-- out-in: сначала уходит старый, потом появляется новый --> <Transition name="slide" mode="out-in"> <component :is="currentView" :key="currentView" /> </Transition> <!-- in-in: сначала появляется новый, потом уходит старый --> <Transition name="fade" mode="in-out"> <div v-if="isLoggedIn" key="user-panel">Панель пользователя</div> <div v-else key="login-form">Форма входа</div> </Transition> </template>

mode="out-in" — самый распространённый. Создаёт ощущение замены контента. Используйте его при переключении видов, вкладок, шагов форм.

Обратите внимание на :key — без него Vue может повторно использовать DOM-элемент и не запустить анимацию.

#appear: анимация при первом рендере

По умолчанию <Transition> не анимирует элемент при первом рендере страницы. Атрибут appear включает эту анимацию:

<template> <Transition name="fade" appear> <div class="hero-section">Добро пожаловать!</div> </Transition> </template> <style> /* Можно задать отдельные классы для первоначального появления */ .fade-appear-from { opacity: 0; transform: translateY(-20px); } .fade-appear-active { transition: opacity 0.5s ease, transform 0.5s ease; } .fade-appear-to { opacity: 1; transform: translateY(0); } /* Если не заданы appear-классы, используются enter-классы */ </style>

Хорошая практика для лендингов и презентационных страниц — добавляйте appear к hero-секциям и ключевым блокам.

#<TransitionGroup> для анимации списков

<TransitionGroup> анимирует добавление, удаление и перемещение элементов в списке. Он рендерит реальный DOM-элемент (по умолчанию <span>), что можно изменить через tag:

<template> <div class="controls"> <button @click="addItem">Добавить</button> <button @click="shuffle">Перемешать</button> </div> <!-- tag="ul" рендерит <ul> вместо дефолтного <span> --> <TransitionGroup name="list" tag="ul" class="item-list"> <li v-for="item in items" :key="item.id" class="item"> {{ item.name }} </li> </TransitionGroup> </template> <script setup lang="ts"> import { ref } from 'vue' const items = ref([ { id: 1, name: 'Первый' }, { id: 2, name: 'Второй' }, { id: 3, name: 'Третий' }, ]) function addItem() { const id = Date.now() items.value.push({ id, name: `Элемент ${id}` }) } function shuffle() { items.value = [...items.value].sort(() => Math.random() - 0.5) } </script> <style> .list-enter-active, .list-leave-active { transition: all 0.4s ease; } .list-enter-from { opacity: 0; transform: translateX(-30px); } .list-leave-to { opacity: 0; transform: translateX(30px); } /* Ключевой класс для плавного перемещения существующих элементов */ .list-move { transition: transform 0.4s ease; } /* При удалении нужно вывести элемент из потока, чтобы остальные плавно встали */ .list-leave-active { position: absolute; } </style>

Класс move-class (или автоматически ${name}-move) управляет анимацией перемещения. Без него элементы при перестановке прыгают мгновенно. Трюк с position: absolute во время leave-active необходим — иначе удаляемый элемент занимает место и остальные не могут плавно переместиться.

#JavaScript-хуки для анимаций

Когда CSS недостаточно, используйте JavaScript-хуки. Они дают полный контроль над каждым этапом:

<template> <Transition @before-enter="onBeforeEnter" @enter="onEnter" @after-enter="onAfterEnter" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" :css="false" > <div v-if="show" class="panel">Контент</div> </Transition> </template> <script setup lang="ts"> import { ref } from 'vue' const show = ref(false) function onBeforeEnter(el: Element) { // Установить начальное состояние до анимации ;(el as HTMLElement).style.opacity = '0' ;(el as HTMLElement).style.transform = 'scale(0.9)' } function onEnter(el: Element, done: () => void) { // done() вызывается когда анимация завершена const htmlEl = el as HTMLElement htmlEl.animate( [ { opacity: 0, transform: 'scale(0.9)' }, { opacity: 1, transform: 'scale(1)' } ], { duration: 300, easing: 'ease-out' } ).onfinish = done } function onLeave(el: Element, done: () => void) { const htmlEl = el as HTMLElement htmlEl.animate( [ { opacity: 1, transform: 'scale(1)' }, { opacity: 0, transform: 'scale(0.9)' } ], { duration: 200, easing: 'ease-in' } ).onfinish = done } // Пустые хуки для TypeScript function onAfterEnter(el: Element) {} function onBeforeLeave(el: Element) {} function onAfterLeave(el: Element) {} </script>

Атрибут :css="false" отключает автоматическое добавление CSS-классов — Vue не будет искать .v-enter-from и т.д. Используйте когда управляете анимацией полностью через JS.

Обязательно вызывайте done() в хуках enter и leave — иначе Vue не поймёт что переход завершён.

#KeepAlive: кэширование компонентов

<KeepAlive> сохраняет состояние компонентов при переключении между ними. Без него каждое переключение — это полное уничтожение и создание заново:

<template> <div class="tabs"> <button v-for="tab in tabs" :key="tab" @click="activeTab = tab" :class="{ active: activeTab === tab }" > {{ tab }} </button> </div> <!-- KeepAlive кэширует компоненты между переключениями --> <KeepAlive :include="['ProfileTab', 'SettingsTab']" :max="5"> <component :is="currentComponent" /> </KeepAlive> </template> <script setup lang="ts"> import { ref, computed } from 'vue' import ProfileTab from './ProfileTab.vue' import SettingsTab from './SettingsTab.vue' import AnalyticsTab from './AnalyticsTab.vue' const activeTab = ref('profile') const tabs = ['profile', 'settings', 'analytics'] const components = { profile: ProfileTab, settings: SettingsTab, analytics: AnalyticsTab, } const currentComponent = computed(() => components[activeTab.value as keyof typeof components]) </script>

Параметры include и exclude фильтруют кэшируемые компоненты по имени (совпадает с name в defineOptions или названием файла). max ограничивает размер кэша — при превышении наименее используемый компонент уничтожается.

Кэшированные компоненты получают специальные lifecycle хуки:

<script setup lang="ts"> import { onActivated, onDeactivated } from 'vue' // Вызывается когда компонент становится активным (не только при монтировании) onActivated(() => { console.log('Вкладка стала активной — обновим данные') refreshData() }) // Вызывается когда компонент скрывается (не уничтожается!) onDeactivated(() => { console.log('Вкладка скрыта — остановим polling') stopPolling() }) </script>

#GSAP интеграция

GSAP (GreenSock Animation Platform) — самая мощная библиотека JS-анимаций. Интегрируется с Vue через JavaScript-хуки:

npm install gsap
<template> <Transition @enter="gsapEnter" @leave="gsapLeave" :css="false" > <div v-if="show" ref="cardRef" class="card"> <h2>Карточка с GSAP анимацией</h2> <p>Плавное появление с эффектом</p> </div> </Transition> </template> <script setup lang="ts"> import { ref } from 'vue' import { gsap } from 'gsap' const show = ref(false) function gsapEnter(el: Element, done: () => void) { gsap.from(el, { duration: 0.5, opacity: 0, y: -50, scale: 0.95, ease: 'power2.out', onComplete: done }) } function gsapLeave(el: Element, done: () => void) { gsap.to(el, { duration: 0.3, opacity: 0, y: 50, scale: 0.95, ease: 'power2.in', onComplete: done }) } </script>

GSAP Timeline позволяет создавать сложные последовательности:

function gsapEnter(el: Element, done: () => void) { const tl = gsap.timeline({ onComplete: done }) tl.from(el, { opacity: 0, duration: 0.3 }) .from(el.querySelector('h2'), { y: -20, opacity: 0, duration: 0.2 }, '-=0.1') .from(el.querySelector('p'), { y: 20, opacity: 0, duration: 0.2 }, '-=0.1') }

#Анимации маршрутов в Vue Router

Для анимации переходов между страницами используйте слот <RouterView> с <Transition>:

<!-- App.vue или Layout.vue --> <template> <RouterView v-slot="{ Component, route }"> <Transition :name="route.meta.transition as string || 'fade'" mode="out-in" > <!-- :key обеспечивает что при смене маршрута компонент пересоздаётся --> <component :is="Component" :key="route.path" /> </Transition> </RouterView> </template>

В метаданных маршрутов задайте тип анимации:

const routes = [ { path: '/dashboard', component: Dashboard, meta: { transition: 'slide-left' } }, { path: '/settings', component: Settings, meta: { transition: 'slide-right' } } ]

#CSS переменные и v-bind в style

Vue 3 позволяет использовать реактивные значения прямо в CSS через v-bind():

<script setup lang="ts"> import { ref } from 'vue' const duration = ref(300) // миллисекунды const primaryColor = ref('#3b82f6') </script> <template> <div class="animated-box">Анимированный блок</div> </template> <style scoped> .animated-box { /* v-bind() использует реактивное значение из setup */ transition: all v-bind(duration + 'ms') ease; background-color: v-bind(primaryColor); } </style>

Это особенно полезно для компонентов с настраиваемыми анимациями через props.

#Производительность CSS анимаций

Анимируйте только transform и opacity — они не вызывают layout reflow и обрабатываются GPU:

/* Хорошо — только transform и opacity */ .card-enter-from { opacity: 0; transform: translateY(-20px) scale(0.95); } /* Плохо — width и height вызывают reflow на каждом кадре */ .card-enter-from { width: 0; height: 0; }

Подсказка браузеру об интенсивной анимации. will-change нужно использовать экономно — только для элементов, которые гарантированно анимируются:

.animated-element { will-change: transform, opacity; }

Добавляйте will-change через JavaScript непосредственно перед анимацией и убирайте после — это правильнее, чем держать его постоянно.

Далее: Тестирование Vue компонентов