Przejdź bezpośrednio do treści

Transition

Vue oferuje dwa wbudowane komponenty, które mogą pomóc w pracy z przejściami i animacjami w odpowiedzi na zmiany stanu:

  • <Transition> służący do stosowania animacji, gdy element lub komponent jest dodawany lub usuwany z DOM. Jest to omówione na tej stronie.

  • <TransitionGroup> służący do stosowania animacji, gdy element lub komponent jest wstawiany, usuwany lub przemieszczany w liście v-for. Jest to omówione w następnym rozdziale.

Oprócz tych dwóch komponentów możemy również stosować animacje w Vue używając innych technik, takich jak przełączanie klas CSS lub animacje sterowane stanem poprzez wiązanie stylów. Te dodatkowe techniki są omówione w rozdziale Techniki Animacji.

Komponent <Transition>

<Transition> jest wbudowanym komponentem: oznacza to, że jest dostępny w szablonie każdego komponentu bez konieczności jego rejestracji. Może być używany do zastosowania animacji wejścia i wyjścia na elementach lub komponentach przekazanych do niego przez domyślny slot. Wejście lub wyjście może być wyzwolone przez jedno z następujących:

  • Renderowanie warunkowe poprzez v-if
  • Wyświetlanie warunkowe poprzez v-show
  • Przełączanie komponentów dynamicznych poprzez specjalny element <component>
  • Zmiana specjalnego atrybutu key

Oto przykład najbardziej podstawowego użycia:

template
<button @click="show = !show">Przełącz</button>
<Transition>
  <p v-if="show">witaj</p>
</Transition>
css
/* wyjaśnimy działanie tych klas w następnej kolejności! */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

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

witaj

TIP

<Transition> obsługuje tylko pojedynczy element lub komponent jako zawartość swojego slotu. Jeśli zawartością jest komponent, musi on również posiadać tylko jeden element główny.

Gdy element w komponencie <Transition> jest wstawiany lub usuwany, dzieje się co następuje:

  1. Vue automatycznie wykryje, czy element docelowy ma zastosowane przejścia lub animacje CSS. Jeśli tak, szereg klas przejścia CSS zostanie dodanych / usuniętych w odpowiednich momentach.

  2. Jeśli istnieją nasłuchiwacze hooków JavaScript, zostaną one wywołane w odpowiednich momentach.

  3. Jeśli nie wykryto przejść / animacji CSS i nie dostarczono hooków JavaScript, operacje DOM dotyczące wstawienia i/lub usunięcia zostaną wykonane w następnej klatce animacji przeglądarki.

Przejścia oparte na CSS

Klasy przejścia

Istnieje sześć klas stosowanych dla przejść wejścia / wyjścia.

Diagram Przejścia

  1. v-enter-from: Stan początkowy dla wejścia. Dodawany przed wstawieniem elementu, usuwany jedną klatkę po wstawieniu elementu.

  2. v-enter-active: Stan aktywny dla wejścia. Stosowany podczas całej fazy wchodzenia. Dodawany przed wstawieniem elementu, usuwany gdy przejście/animacja się kończy. Ta klasa może być używana do zdefiniowania czasu trwania, opóźnienia i krzywej łagodności dla przejścia wejścia.

  3. v-enter-to: Stan końcowy dla wejścia. Dodawany jedną klatkę po wstawieniu elementu (w tym samym czasie, gdy v-enter-from jest usuwany), usuwany gdy przejście/animacja się kończy.

  4. v-leave-from: Stan początkowy dla wyjścia. Dodawany natychmiast po wywołaniu przejścia wyjścia, usuwany po jednej klatce.

  5. v-leave-active: Stan aktywny dla wyjścia. Stosowany podczas całej fazy wychodzenia. Dodawany natychmiast po wywołaniu przejścia wyjścia, usuwany gdy przejście/animacja się kończy. Ta klasa może być używana do zdefiniowania czasu trwania, opóźnienia i krzywej łagodności dla przejścia wyjścia.

  6. v-leave-to: Stan końcowy dla wyjścia. Dodawany jedną klatkę po wywołaniu przejścia wyjścia (w tym samym czasie, gdy v-leave-from jest usuwany), usuwany gdy przejście/animacja się kończy.

v-enter-active i v-leave-active dają nam możliwość określenia różnych krzywych łagodności dla przejść wejścia / wyjścia, czego przykład zobaczymy w następnych sekcjach.

Nazwane przejścia

Przejście może zostać nazwane za pomocą właściwości name:

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

Dla nazwanego przejścia, jego klasy przejścia będą poprzedzone jego nazwą zamiast v. Na przykład, zastosowana klasa dla powyższego przejścia będzie fade-enter-active zamiast v-enter-active. CSS dla przejścia fade powinien wyglądać następująco:

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

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

Przejścia CSS

<Transition> jest najczęściej używany w połączeniu z natywnymi przejściami CSS, jak widać w podstawowym przykładzie powyżej. Właściwość CSS transition jest skrótem, który pozwala nam określić wiele aspektów przejścia, w tym właściwości, które powinny być animowane, czas trwania przejścia oraz krzywe łagodności.

Oto bardziej zaawansowany przykład, który pokazuje przejścia wielu właściwości, z różnymi czasami trwania i krzywymi łagodności dla wejścia i wyjścia:

template
<Transition name="slide-fade">
  <p v-if="show">witaj</p>
</Transition>
css
/*
  Animacje wejścia i wyjścia mogą używać różnych
  czasów trwania i funkcji czasowych.
*/
.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;
}

witaj

Animacje CSS

Natywne animacje CSS są stosowane w ten sam sposób co przejścia CSS, z tą różnicą, że *-enter-from nie jest usuwane natychmiast po wstawieniu elementu, ale przy zdarzeniu animationend.

Dla większości animacji CSS możemy po prostu zadeklarować je w klasach *-enter-active i *-leave-active. Oto przykład:

template
<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Witaj, oto tekst z efektem odbijania!
  </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);
  }
}

Cześć, oto tekst z podskokami!

Niestandardowe klasy przejścia

Możesz również określić niestandardowe klasy przejścia, przekazując następujące właściwości do <Transition>:

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

Zastąpią one konwencjonalne nazwy klas. Jest to szczególnie przydatne, gdy chcesz połączyć system przejść Vue z istniejącą biblioteką animacji CSS, taką jak Animate.css:

template
<!-- zakładając, że Animate.css jest dołączone na stronie -->
<Transition
  name="custom-classes"
  enter-active-class="animate__animated animate__tada"
  leave-active-class="animate__animated animate__bounceOutRight"
>
 <p v-if="show">witaj</p>
</Transition>

Używanie przejść i animacji razem

Vue musi podpiąć nasłuchiwanie zdarzeń, aby wiedzieć, kiedy przejście się zakończyło. Może to być transitionend lub animationend, w zależności od zastosowanych reguł CSS. Jeśli używasz tylko jednego z nich, Vue automatycznie wykryje odpowiedni typ.

Jednak w niektórych przypadkach możesz chcieć użyć obu na tym samym elemencie, na przykład mając animację CSS wyzwalaną przez Vue wraz z efektem przejścia CSS po najechaniu myszką. W takich przypadkach musisz jawnie zadeklarować typ, którym Vue ma się zajmować, przekazując prop type z wartością animation lub transition:

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

Zagnieżdżone przejścia i jawne czasy trwania przejść

Mimo że klasy przejść są stosowane tylko do bezpośredniego elementu potomnego w <Transition>, możemy tworzyć przejścia dla elementów zagnieżdżonych używając zagnieżdżonych selektorów CSS:

template
<Transition name="nested">
  <div v-if="show" class="outer">
    <div class="inner">
      Witaj
    </div>
  </div>
</Transition>
css
/* reguły celujące w zagnieżdżone elementy */
.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;
}

/* ... pozostałe niezbędne CSS pominięto */

Możemy nawet dodać opóźnienie przejścia dla zagnieżdżonego elementu podczas wejścia, co tworzy rozłożoną w czasie sekwencję animacji wejścia:

css
/* opóźnienie wejścia zagnieżdżonego elementu dla efektu rozłożenia w czasie */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}

Jednakże, tworzy to niewielki problem. Domyślnie komponent <Transition> próbuje automatycznie ustalić, kiedy przejście się zakończyło, nasłuchując pierwszego zdarzenia transitionend lub animationend na głównym elemencie przejścia. W przypadku zagnieżdżonego przejścia, pożądanym zachowaniem powinno być oczekiwanie, aż zakończą się przejścia wszystkich wewnętrznych elementów.

W takich przypadkach możesz określić jawny czas trwania przejścia (w milisekundach) używając propa duration w komponencie <transition>. Całkowity czas trwania powinien odpowiadać sumie opóźnienia i czasu trwania przejścia elementu wewnętrznego:

template
<Transition :duration="550">...</Transition>
Witaj

Pełny przykład na Playground

Jeśli to konieczne, możesz również określić osobne wartości dla czasów trwania wejścia i wyjścia, używając obiektu:

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

Kwestie wydajnościowe

Możesz zauważyć, że pokazane powyżej animacje używają głównie właściwości takich jak transform i opacity. Te właściwości są wydajne w animacji, ponieważ:

  1. Nie wpływają na układ dokumentu podczas animacji, więc nie wyzwalają kosztownych obliczeń układu CSS przy każdej klatce animacji.

  2. Większość nowoczesnych przeglądarek może wykorzystywać sprzętową akcelerację GPU podczas animowania właściwości transform.

W porównaniu, właściwości takie jak height lub margin będą wyzwalać przeliczanie układu CSS, przez co są znacznie bardziej kosztowne w animacji i powinny być używane z rozwagą.

Hooki JavaScript

Możesz wpięć się w proces przejścia za pomocą JavaScriptu, nasłuchując zdarzeń na komponencie <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
// wywoływane zanim element zostanie wstawiony do DOM.
// użyj tego, aby ustawić stan "enter-from" elementu
function onBeforeEnter(el) {}

// wywoływane jedną klatkę po wstawieniu elementu.
// użyj tego, aby rozpocząć animację wejścia.
function onEnter(el, done) {
  // wywołaj callback done, aby zasygnalizować koniec przejścia
  // opcjonalne, jeśli używane w połączeniu z CSS
  done()
}

// wywoływane, gdy przejście wejścia zostało zakończone.
function onAfterEnter(el) {}

// wywoływane, gdy przejście wejścia zostało anulowane przed ukończeniem.
function onEnterCancelled(el) {}

// wywoływane przed hookiem leave.
// W większości przypadków powinieneś po prostu użyć hooka leave
function onBeforeLeave(el) {}

// wywoływane, gdy rozpoczyna się przejście wyjścia.
// użyj tego, aby rozpocząć animację wyjścia.
function onLeave(el, done) {
  // wywołaj callback done, aby zasygnalizować koniec przejścia
  // opcjonalne, jeśli używane w połączeniu z CSS
  done()
}

// wywoływane, gdy przejście wyjścia zostało zakończone i
// element został usunięty z DOM.
function onAfterLeave(el) {}

// dostępne tylko przy przejściach v-show
function onLeaveCancelled(el) {}
js
export default {
  // ...
  methods: {
    // wywoływane zanim element zostanie wstawiony do DOM.
    // użyj tego, aby ustawić stan "enter-from" elementu
    onBeforeEnter(el) {},

    // wywoływane jedną klatkę po wstawieniu elementu.
    // użyj tego, aby rozpocząć animację.
    onEnter(el, done) {
      // wywołaj callback done, aby zasygnalizować koniec przejścia
      // opcjonalne, jeśli używane w połączeniu z CSS
      done()
    },

    // wywoływane, gdy przejście wejścia zostało zakończone.
    onAfterEnter(el) {},

    // wywoływane, gdy przejście wejścia zostało anulowane przed ukończeniem.
    onEnterCancelled(el) {},

    // wywoływane przed hookiem leave.
    // W większości przypadków powinieneś po prostu użyć hooka leave.
    onBeforeLeave(el) {},

    // wywoływane, gdy rozpoczyna się przejście wyjścia.
    // użyj tego, aby rozpocząć animację wyjścia.
    onLeave(el, done) {
      // wywołaj callback done, aby zasygnalizować koniec przejścia
      // opcjonalne, jeśli używane w połączeniu z CSS
      done()
    },

    // wywoływane, gdy przejście wyjścia zostało zakończone i
    // element został usunięty z DOM.
    onAfterLeave(el) {},

    // dostępne tylko przy przejściach v-show
    onLeaveCancelled(el) {}
  }
}

Te hooki mogą być używane w połączeniu z przejściami/animacjami CSS lub samodzielnie.

Podczas używania przejść opartych tylko na JavaScripcie, zwykle dobrym pomysłem jest dodanie propa :css="false". Jawnie informuje to Vue, aby pominęło automatyczne wykrywanie przejść CSS. Poza tym, że jest to nieco bardziej wydajne, zapobiega to również przypadkowemu interferowaniu reguł CSS z przejściem:

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

Przy :css="false" jesteśmy również w pełni odpowiedzialni za kontrolowanie, kiedy przejście się kończy. W tym przypadku callbacki done są wymagane dla hooków @enter i @leave. W przeciwnym razie hooki zostaną wywołane synchronicznie, a przejście zakończy się natychmiast.

Oto przykład wykorzystujący bibliotekę GSAP do wykonywania animacji. Oczywiście możesz użyć dowolnej innej biblioteki animacji, na przykład Anime.js lub Motion One:

Przejścia wielokrotnego użytku

Przejścia mogą być używane wielokrotnie dzięki systemowi komponentów Vue. Aby stworzyć przejście wielokrotnego użytku, możemy utworzyć komponent, który opakowuje komponent <Transition> i przekazuje zawartość slotu:

vue
<!-- MyTransition.vue -->
<script>
// Logika hooków JavaScript...
</script>

<template>
 <!-- opakowuje wbudowany komponent Transition -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- przekazuje zawartość slotu -->
  </Transition>
</template>

<style>
/*
 Niezbędny CSS...
 Uwaga: unikaj używania <style scoped> tutaj, ponieważ
 nie stosuje się on do zawartości slotu.
*/
</style>

Teraz MyTransition może być importowany i używany tak jak wersja wbudowana:

template
<MyTransition>
  <div v-if="show">Witaj</div>
</MyTransition>

Przejście podczas pierwszego pojawienia się

Jeśli chcesz również zastosować przejście podczas początkowego renderowania węzła, możesz dodać prop appear:

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

Przejście między elementami

Oprócz przełączania elementu za pomocą v-if / v-show, możemy także wykonywać przejścia między dwoma elementami używając v-if / v-else / v-else-if, pod warunkiem że upewnimy się, że w danym momencie wyświetlany jest tylko jeden element:

template
<Transition>
  <button v-if="docState === 'saved'">Edytuj</button>
  <button v-else-if="docState === 'edited'">Zapisz</button>
  <button v-else-if="docState === 'editing'">Anuluj</button>
</Transition>
Kliknij, aby przełączać stany:

Pełny przykład na Playground

Tryby przejścia

W poprzednim przykładzie elementy wchodzące i wychodzące są animowane w tym samym czasie i musieliśmy ustawić im position: absolute, aby uniknąć problemów z układem, gdy oba elementy są obecne w DOM.

Jednak w niektórych przypadkach nie jest to możliwe lub po prostu nie jest pożądanym zachowaniem. Możemy chcieć, aby element wychodzący został najpierw zanimowany, a element wchodzący został wstawiony dopiero po zakończeniu animacji wyjścia. Ręczne orkiestrowanie takich animacji byłoby bardzo skomplikowane - na szczęście możemy włączyć to zachowanie, przekazując do <Transition> prop mode:

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

Oto poprzednia demonstracja z mode="out-in":

Kliknij, aby przełączać stany:

<Transition> obsługuje również mode="in-out", chociaż jest on używany znacznie rzadziej.

Przejście między komponentami

<Transition> może być również używany wokół komponentów dynamicznych:

template
<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>
Komponent A

Dynamiczne przejścia

Właściwości <Transition> takie jak name mogą być również dynamiczne! Pozwala nam to dynamicznie zastosować różne przejścia w zależności od zmiany stanu:

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

Może to być przydatne, gdy zdefiniowałeś przejścia/animacje CSS używając konwencji klas przejścia Vue i chcesz się między nimi przełączać.

Możesz również zastosować różne zachowania w hookach przejścia JavaScript w zależności od aktualnego stanu twojego komponentu. Ostatecznie, najlepszym sposobem tworzenia dynamicznych przejść jest użycie komponentów przejścia wielokrotnego użytku, które przyjmują props do zmiany charakteru używanych przejść. Może to brzmieć banalnie, ale jedynym ograniczeniem jest naprawdę tylko twoja wyobraźnia.

Przejścia z atrybutem Key

Czasami musisz wymusić ponowne wyrenderowanie elementu DOM, aby przejście mogło wystąpić.

Weźmy na przykład ten komponent licznika:

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>

Gdybyśmy pominęli atrybut key, tylko węzeł tekstowy zostałby zaktualizowany i w związku z tym nie wystąpiłoby żadne przejście. Jednak z obecnym atrybutem key, Vue wie, że ma utworzyć nowy element span za każdym razem, gdy zmienia się count, a dzięki temu komponent Transition ma 2 różne elementy, między którymi może wykonać przejście.


Powiązane

TransitionJest załadowany