Skip to content

Transition

Vue предлагает два встроенных компонента, которые помогают работать с переходами и анимацией в ответ на изменение состояния:

  • <Transition> для применения анимации элемента или компонента при его добавлении и удалении из DOM. Об этом рассказывается на этой странице.

  • <TransitionGroup> для применения анимации при вставке, удалении или перемещении элемента или компонента в списке v-for. Этому посвящена следующая глава.

Помимо этих двух компонентов, во Vue можно применять анимацию и с помощью других приёмов, таких как переключение CSS-классов или анимация, управляемая состоянием с помощью привязки стилей. Эти дополнительные приёмы рассматриваются в главе Техники анимации.

Компонент <Transition>

<Transition> является встроенным компонентом: это означает, что он доступен в шаблоне любого компонента без необходимости его регистрации. Он может использоваться для применения анимации появлении и исчезновении к элементам или компонентам, передаваемым ему через слот по умолчанию. Появление или исчезновение может быть вызвано одним из следующих действий:

  • Условное отображение через v-if
  • Условное отображение с помощью v-show
  • Переключение динамических компонентов с помощью специального элемента <component>
  • Изменение специального атрибута key

Это пример наиболее простого использования:

template
<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">привет</p>
</Transition>
css
/* мы объясним, что делают эти классы дальше! */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

привет

Совет

<Transition> поддерживает только один элемент или компонент в качестве содержимого своего слота. Если содержимое является компонентом, то компонент также должен иметь только один единственный корневой элемент.

Когда элемент в компоненте <Transition> вставляется или удаляется, происходит следующее:

  1. Vue автоматически определяет, есть ли в целевом элементе CSS-переходы или анимация. Если это так, то в соответствующие моменты времени будут добавлены/удалены несколько классов CSS-переходов.

  2. Если есть слушатели для JavaScript хуков, то эти хуки будут вызываться в соответствующие моменты времени.

  3. Если переходы/анимации CSS не обнаружены и хуки JavaScript не предоставлены, операции DOM по вставке и/или удалению будут выполняться в следующем кадре анимации браузера.

Переходы на основе CSS

Классы перехода

Для переходов появления/исчезновения применяются шесть классов.

Диаграмма переходов

  1. v-enter-from: Начало анимации появления элемента. Добавляется перед вставкой элемента, удаляется через один кадр после вставки элемента.

  2. v-enter-active: Активное состояние появления элемента. Этот класс остаётся на элементе в течение всей анимации появления. Добавляется перед вставкой элемента, удаляется по завершении перехода/анимации. Этот класс можно использовать для установки длительности, задержки или функции плавности (easing curve) анимации появления.

  3. v-enter-to: Завершение анимации появления элемента. Добавляется через один кадр после вставки элемента (тогда же удаляется v-enter-from), а удаляется после завершения перехода или анимации.

  4. v-leave-from: Начало анимации исчезновения элемента. Добавляется сразу после вызова анимации исчезновения, а удаляется через один кадр.

  5. v-leave-active: Активное состояние анимации исчезновения. Этот класс остаётся на элементе в течение всей анимации исчезновения. Он добавляется при вызове анимации исчезновения, а удаляется после завершения перехода или анимации. Этот класс можно использовать для установки длительности, задержки или функции плавности (easing curve) анимации исчезновения.

  6. v-leave-to: Завершение анимации исчезновения элемента. Добавляется через один кадр после вызова анимации исчезновения (тогда же удаляется v-leave-from), а удаляется после завершения перехода или анимации.

Классы v-enter-active и v-leave-active позволяют устанавливать функции плавности для переходов появления и исчезновения элемента. Пример использования рассмотрим ниже.

Именованные переходы

Переход может быть назван с помощью свойства name:

template
<Transition name="fade">
  ...
</Transition>

Для именованного перехода его классы переходов будут иметь префикс с названием, а не v. Например, применяемый класс для приведенного выше перехода будет fade-enter-active, а не v-enter-active. CSS для перехода fade должен выглядеть следующим образом:

css
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

CSS-переходы

<Transition> чаще всего используется в сочетании с собственными CSS-переходами, как показано в базовом примере выше. CSS-свойство transition - это сокращение, позволяющее указать множество аспектов перехода, включая свойства, которые должны быть анимированы, длительность перехода и функции плавности.

Приведем более сложный пример, в котором переходы осуществляются по нескольким свойствам, с различными длительностями и функциями плавности для входа и выхода:

template
<Transition name="slide-fade">
  <p v-if="show">привет</p>
</Transition>
css
/*
  Анимации появления и исчезновения могут иметь
  различные продолжительности и функции плавности.
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

привет

CSS-анимации

Собственные CSS-анимации применяются таким же образом, что и CSS-переходы. Они отличаются лишь тем, что *-enter-from удаляется не сразу после вставки элемента, а при наступлении события animationend.

Для большинства CSS-анимаций мы можем просто объявить их в классах *-enter-active и *-leave-active. Вот пример:

template
<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Привет, вот какой-то задорный текст!
  </p>
</Transition>
css
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}

Привет, вот какой-то задорный текст!

Пользовательские классы переходов

Вы также можете указать пользовательские классы переходов, передав в <Transition> следующие входные параметры:

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class

Они будут заменять обычные имена классов. Это особенно полезно, если вы хотите объединить систему переходов Vue с существующей библиотекой анимации CSS, например Animate.css:

template
<!-- при условии, что Animate.css добавлен на странице -->
<Transition
  name="custom-classes"
  enter-active-class="animate__animated animate__tada"
  leave-active-class="animate__animated animate__bounceOutRight"
>
  <p v-if="show">привет</p>
</Transition>

Совместное использование переходов и анимаций

Для определения завершения анимации Vue использует слушатели событий с типом transitionend или animationend, в зависимости от типа применяемых CSS-правил. Если используется только один подход из них, Vue определит правильный тип автоматически.

Однако, в некоторых случаях может потребоваться использовать оба подхода на одном элементе. Например CSS-анимация, запущенная Vue, может применяться с эффектом CSS-перехода при наведении на элемент. В таких случаях потребуется явное указание типа события, на которое должен ориентироваться Vue. Для этого нужно использовать атрибут type со значением animation или transition:

template
<Transition type="animation">...</Transition>

Вложенные переходы и явные длительности переходов

Хотя классы перехода применяются только к непосредственному дочернему элементу <Transition>, мы можем переходить к вложенным элементам с помощью вложенных CSS-селекторов:

template
<Transition name="nested">
  <div v-if="show" class="outer">
    <div class="inner">
      Привет
    </div>
  </div>
</Transition>
css
/* правила, нацеленные на вложенные элементы */
.nested-enter-active .inner,
.nested-leave-active .inner {
  transition: all 0.3s ease-in-out;
}

.nested-enter-from .inner,
.nested-leave-to .inner {
  transform: translateX(30px);
  opacity: 0;
}

/* ... другие необходимые CSS не указаны */

Мы можем даже добавить задержку перехода во вложенный элемент при входе, что создаст ступенчатую последовательность анимации входа:

css
/* задержка появления вложенного элемента для эффекта */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}

Однако при этом возникает небольшая проблема. По умолчанию компонент <Transition> пытается автоматически определить момент завершения перехода, слушая первое событие transitionend или animationend на корневом элементе перехода. При вложенном переходе желательным поведением будет ожидание завершения переходов всех внутренних элементов.

В таких случаях можно указать явную длительность перехода (в миллисекундах) с помощью параметра duration компонента <transition>. Общая длительность должна соответствовать длительности задержки плюс длительности перехода внутреннего элемента:

template
<Transition :duration="550">...</Transition>
Привет

Попробовать в песочнице

При необходимости можно также задать отдельные значения для продолжительности входа и выхода с помощью объекта:

template
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>

Соображения по производительности

Вы можете заметить, что в приведенных выше анимациях в основном используются такие свойства, как transform и opacity. Эти свойства эффективны для анимации, поскольку:

  1. Они не влияют на макет документа во время анимации, поэтому не вызывают дорогостоящих расчётов шаблона CSS на каждом кадре анимации.

  2. Большинство современных браузеров могут использовать аппаратное ускорение GPU при transform.

Для сравнения, такие свойства, как height или margin вызывают шаблон CSS, поэтому их анимировать гораздо дороже, и использовать их следует с осторожностью.

JavaScript хуки

Вы можете подключиться к процессу перехода с помощью JavaScript, прослушивая события на компоненте <Transition>:

html
<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>
js
// вызывается перед вставкой элемента в DOM.
// используется для установки состояния "enter-from" элемента
function onBeforeEnter(el) {}

// вызывается через один кадр после вставки элемента.
// используйте его для запуска анимации входа.
function onEnter(el, done) {
  // вызов обратного вызова done для индикации окончания перехода
  // необязателен, если используется в сочетании с CSS
  done()
}

// вызывается по завершении перехода enter.
function onAfterEnter(el) {}

// вызывается, когда переход enter отменяется до завершения.
function onEnterCancelled(el) {}

// вызывается перед хуком leave.
// В большинстве случаев следует использовать только хук leave
function onBeforeLeave(el) {}

// вызывается, когда начинается переход к leave.
// используйте его для запуска анимации ухода.
function onLeave(el, done) {
  // вызов обратного вызова done для индикации окончания перехода
  // необязательно, если используется в сочетании с CSS
  done()
}

// вызывается, когда переход к leave завершен
// элемент был удален из DOM.
function onAfterLeave(el) {}

// доступно только при использовании переходов v-show
function onLeaveCancelled(el) {}
js
export default {
  // ...
  methods: {
    // вызывается перед вставкой элемента в DOM.
    // используется для установки состояния "вход-выход" элемента
    onBeforeEnter(el) {},

    // вызывается через один кадр после вставки элемента.
    // используйте это, чтобы запустить анимацию.
    onEnter(el, done) {
      // вызов обратного вызова done для индикации окончания перехода
      // необязательно, если используется в сочетании с CSS
      done()
    },

    // вызывается по завершении перехода на enter.
    onAfterEnter(el) {},

    // вызывается, когда переход enter отменяется до завершения.
    onEnterCancelled(el) {},

    // вызывается перед хуком leave.
    // В большинстве случаев вам следует просто использовать хук leave.
    onBeforeLeave(el) {},

    // вызывается, когда начинается переход к leave.
    // используйте это, чтобы запустить анимацию ухода.
    onLeave(el, done) {
      // вызовите обратный вызов done, чтобы указать конец перехода
      // необязательно, если используется в сочетании с CSS
      done()
    },

    // вызывается, когда переход к leave завершен
    // и элемент был удален из DOM.
    onAfterLeave(el) {},

    // доступно только с переходами v-show
    onLeaveCancelled(el) {}
  }
}

Эти хуки могут использоваться как в сочетании с CSS-переходами/анимацией, так и самостоятельно.

При использовании переходов, основанных только на JavaScript, обычно рекомендуется добавить параметр :css="false". Это явно указывает Vue на необходимость пропускать автоматическое определение CSS-переходов. Помимо того, что это несколько повышает производительность, это также предотвращает случайное вмешательство CSS-правил в переход:

template
<Transition
  ...
  :css="false"
>
  ...
</Transition>

При использовании :css="false" мы также полностью отвечаем за контроль окончания перехода. В этом случае обратные вызовы done необходимы для хуков @enter и @leave. В противном случае хуки будут вызываться синхронно и переход завершится немедленно.

Вот демонстрация использования GSAP library для выполнения анимации. Конечно, вы можете использовать любую другую библиотеку анимации, например Anime.js или Motion One.

Переиспользуемые переходы

Переходы можно повторно использовать через систему компонентов Vue. Чтобы создать повторно используемый переход, мы можем создать компонент, который обертывает компонент <Transition> и передает содержимое слота:

vue
<!-- MyTransition.vue -->
<script>
// Логика хуков JavaScript...
</script>

<template>
  <!-- обернуть встроенный компонент Transition -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- передать содержимое слота -->
  </Transition>
</template>

<style>
/*
  Необходимый CSS...
  Примечание: избегайте использования здесь <style scoped>,
  так как это не относится к содержимому слота.
*/
</style>

Теперь MyTransition можно импортировать и использовать так же, как встроенную версию:

template
<MyTransition>
  <div v-if="show">привет</div>
</MyTransition>

Переход при появлении

Если вы также хотите применить переход при начальном рендеринге узла, вы можете добавить атрибут appear:

template
<Transition appear>
  ...
</Transition>

Переход между элементами

Помимо переключения элемента с помощью v-if / v-show, мы также можем осуществлять переход между двумя элементами с помощью v-if / v-else / v-else-if, при условии, что в каждый момент времени отображается только один элемент:

template
<Transition>
  <button v-if="docState === 'saved'">Редактировать</button>
  <button v-else-if="docState === 'edited'">Сохранить</button>
  <button v-else-if="docState === 'editing'">Отменить</button>
</Transition>
Щелкните мышью для переключения между состояниями:

Попробовать в песочнице

Режимы перехода

В предыдущем примере элементы входа и выхода анимируются одновременно, и нам пришлось сделать position: absolute чтобы избежать проблем с компоновкой, когда оба элемента присутствуют в DOM.

Однако в некоторых случаях это невозможно или просто нежелательно. Мы можем захотеть, чтобы сначала анимировался выходящий элемент, а входящий элемент вставлялся только после завершения анимации выхода. Организовать такую анимацию вручную было бы очень сложно - к счастью, мы можем включить это поведение, передав <Transition> свойство mode:

template
<Transition mode="out-in">
  ...
</Transition>

Вот предыдущая демонстрация с mode="out-in":

Щелкните мышью для переключения между состояниями:

<Transition> также поддерживает режим mode="in-out", хотя он используется гораздо реже.

Переход между компонентами

<Transition> также может использоваться вокруг динамических компонентов:

template
<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>
Компонент A

Динамические переходы

Входные параметры <Transition>, например name, также могут быть динамическими! Это позволяет нам динамически применять различные переходы в зависимости от изменения состояния:

template
<Transition :name="transitionName">
  <!-- ... -->
</Transition>

Это может быть полезно, если вы определили CSS-переходы / анимацию, используя соглашения Vue о классах переходов, и хотите переключаться между ними.

Также можно применять различное поведение в переходах JavaScript-хуков в зависимости от текущего состояния компонента. Наконец, самый продвинутый способ создания динамических переходов - это многократно используемые компоненты переходов, которые принимают входные параметры для изменения характера используемого перехода (переходов). Это может показаться банальным, но на самом деле единственное ограничение - это ваше воображение.

Переходы с атрибутом key

Иногда вам нужно принудительно выполнить повторный рендеринг элемента DOM, чтобы произошел переход.

Возьмем, к примеру, этот компонент счетчика:

vue
<script setup>
import { ref } from 'vue';
const count = ref(0);

setInterval(() => count.value++, 1000);
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>
vue
<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>

Если бы мы исключили атрибут key, обновлялся бы только текстовый узел, и, следовательно, переход не происходил бы. Однако, при наличии атрибута key, Vue знает, что нужно создавать новый элемент span всякий раз, когда изменяется count, таким образом, у компонента Transition есть 2 разных элемента для перехода между ними.


Связанное

TransitionУже загружено