Перейти к содержанию

Transition

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

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

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

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

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

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

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

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

template
<button @click="show = !show">Переключить</button>
<Transition>
  <p v-if="show">hello</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

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

Для переходов «enter/leave» («вход/выход») применяются шесть классов.

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

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

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

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

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

  5. v-leave-active: Активное состояние для выхода. Наносится в течение всего периода выхода. Добавляется сразу при переходе к выходу, удаляется по завершении перехода/анимации. Этот класс можно использовать для определения длительности, задержки и кривой ослабления перехода при выходе.

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

v-enter-active и v-leave-active дают нам возможность задавать различные кривые смягчения для переходов enter/leave, пример которых мы увидим в следующих разделах.

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

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

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

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

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">hello</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">
      Hello
    </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>:

template
<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.
// Используйте это, чтобы установить состояние "вход-выход" элемента
function onBeforeEnter(el) {}

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

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

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

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

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

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

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

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

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

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

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

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

    // вызывается, когда переход к выходу завершен и элемент удален из 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 для выполнения анимации. Разумеется, вы можете использовать любую другую библиотеку анимации, например 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">Hello</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>
Component 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