Kurs Vue.js · Moduł 13: Przejścia i animacje

TransitionGroup - animacja list

3 min czytania
W tej lekcji5

Lista modułów stacji NOVA LAB ciągle się zmienia - dodajemy nowe systemy podtrzymywania życia, usuwamy uszkodzone sensory, sortujemy alerty po priorytecie. Za każdym razem chcemy, aby zmiany w liście były wizualnie płynne. <TransitionGroup> to komponent Vue zaprojektowany specjalnie do animowania elementów renderowanych przez v-for.

Podstawowe użycie

1<TransitionGroup name="list" tag="ul">
2  <li v-for="item in items" :key="item.id">
3    {{ item.name }}
4  </li>
5</TransitionGroup>

Każdy element dodawany do listy animuje się przy wejściu, a usuwany - przy wyjściu. To automatyczne zachowanie bazujące na tych samych klasach CSS co <Transition>.

Kluczowe różnice od <Transition>

<TransitionGroup> różni się od <Transition> w kilku istotnych punktach:

  1. Renderuje element DOM - w przeciwieństwie do <Transition>, <TransitionGroup> domyślnie renderuje element HTML. Kontrolujesz go atrybutem tag: tag="ul", tag="div", tag="section". Jeśli nie chcesz dodatkowego elementu, możesz ustawić tag="template" (Vue 3.5+).

  2. Każdy element musi mieć unikalne :key - Vue musi śledzić tożsamość poszczególnych elementów, aby wiedzieć które dodać, usunąć i przesunąć. Nigdy nie używaj indeksu tablicy jako key!

  3. Obsługuje wiele elementów jednocześnie - nie jest ograniczony do jednego elementu jak <Transition>. Wiele elementów może wchodzić i wychodzić w tym samym czasie.

  4. Dodatkowa klasa -move - animuje przesuwanie elementów, gdy lista się zmienia kolejnością. To unikalna funkcja <TransitionGroup>.

  5. Tryb mode nie jest obsługiwany - ponieważ nie przełączamy między jednym elementem a drugim.

Klasy CSS dla listy

TransitionGroup używa tych samych klas co Transition plus dodatkowa klasa -move:

1/* Wejście nowego elementu */
2.list-enter-active {
3  transition: all 0.5s ease;
4}
5.list-enter-from {
6  opacity: 0;
7  transform: translateX(30px);
8}
9
10/* Wyjście usuwanego elementu */
11.list-leave-active {
12  transition: all 0.5s ease;
13  position: absolute; /* WAŻNE! */
14}
15.list-leave-to {
16  opacity: 0;
17  transform: translateX(-30px);
18}
19
20/* Przesuwanie pozostałych elementów */
21.list-move {
22  transition: transform 0.5s ease;
23}

Move transitions - kluczowa mechanika

To najciekawsza funkcja <TransitionGroup>. Gdy dodajesz lub usuwasz element z listy, pozostałe elementy muszą się przesunąć na nowe pozycje. Vue automatycznie dodaje klasę -move do elementów, które zmieniają pozycję, i animuje ich ruch za pomocą transformacji CSS.

Aby move transitions działały poprawnie, elementy wychodzące muszą mieć position: absolute w klasie -leave-active. Dzięki temu wychodzący element "wychodzi z flow" i pozostałe mogą się płynnie przesunąć:

1.list-leave-active {
2  position: absolute;
3  /* Opcjonalnie: zachowaj szerokość */
4  width: 100%;
5}
6
7.list-move {
8  transition: transform 0.5s ease;
9}

Praktyczny przykład - lista alertów stacji

1<template>
2  <div class="alert-panel">
3    <button @click="addAlert">Dodaj alert</button>
4
5    <TransitionGroup name="alert" tag="div" class="alert-list">
6      <div
7        v-for="alert in alerts"
8        :key="alert.id"
9        class="alert-item"
10        :class="alert.level"
11      >
12        {{ alert.message }}
13        <button @click="removeAlert(alert.id)">x</button>
14      </div>
15    </TransitionGroup>
16  </div>
17</template>
18
19<script setup>
20import { ref } from 'vue'
21
22const alerts = ref([])
23let nextId = 1
24
25function addAlert() {
26  alerts.value.unshift({
27    id: nextId++,
28    message: `Alert #${nextId - 1}: Oxygen level check`,
29    level: Math.random() > 0.5 ? 'warning' : 'critical'
30  })
31}
32
33function removeAlert(id) {
34  alerts.value = alerts.value.filter(a => a.id !== id)
35}
36</script>
1.alert-enter-active, .alert-leave-active {
2  transition: all 0.4s ease;
3}
4.alert-enter-from {
5  opacity: 0;
6  transform: translateX(-30px);
7}
8.alert-leave-to {
9  opacity: 0;
10  transform: translateX(30px);
11}
12.alert-leave-active {
13  position: absolute;
14}
15.alert-move {
16  transition: transform 0.4s ease;
17}

Efekt: nowe alerty wchodzą z lewej, usuwane wychodzą w prawo, a pozostałe płynnie się przesuwają. Dokładnie jak na panelu alertów stacji kosmicznej!

Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const modules = ref([
5  { id: 1, name: 'Generator tlenu', status: 'active' },
6  { id: 2, name: 'Odzysk wody', status: 'active' },
7  { id: 3, name: 'Panele słoneczne', status: 'active' }
8])
9
10let nextId = 4
11
12function addModule() {
13  const names = ['Osłona termiczna', 'Komputer nawigacyjny', 'Przekaźnik łączności', 'Szklarnia']
14  const name = names[Math.floor(Math.random() * names.length)]
15  modules.value.splice(
16    Math.floor(Math.random() * (modules.value.length + 1)),
17    0,
18    { id: nextId++, name, status: 'active' }
19  )
20}
21
22function removeModule(id) {
23  const idx = modules.value.findIndex(m => m.id === id)
24  if (idx > -1) modules.value.splice(idx, 1)
25}
26
27function shuffle() {
28  modules.value = [...modules.value].sort(() => Math.random() - 0.5)
29}
30</script>
31
32<template>
33  <div class="nova-lab">
34    <h1>Demo TransitionGroup</h1>
35
36    <div class="controls">
37      <button @click="addModule" class="btn add">+ Dodaj moduł</button>
38      <button @click="shuffle" class="btn shuffle">Losuj</button>
39    </div>
40
41    <TransitionGroup name="list" tag="ul" class="module-list">
42      <li v-for="mod in modules" :key="mod.id" class="module-item">
43        <span>{{ mod.name }}</span>
44        <button @click="removeModule(mod.id)" class="remove">×</button>
45      </li>
46    </TransitionGroup>
47  </div>
48</template>
49
50<style scoped>
51.nova-lab {
52  background: #0a0e27;
53  color: #00ff88;
54  min-height: 100vh;
55  padding: 20px;
56  font-family: monospace;
57}
58h1 { color: #00ff88; }
59.controls { display: flex; gap: 10px; margin: 15px 0; }
60.btn {
61  border: none;
62  padding: 10px 16px;
63  border-radius: 8px;
64  cursor: pointer;
65  color: white;
66}
67.add { background: #00b4d8; }
68.shuffle { background: #9b59b6; }
69
70.module-list {
71  list-style: none;
72  padding: 0;
73}
74.module-item {
75  display: flex;
76  justify-content: space-between;
77  align-items: center;
78  background: rgba(0, 180, 216, 0.1);
79  border: 1px solid #00b4d8;
80  padding: 12px 16px;
81  margin: 8px 0;
82  border-radius: 8px;
83}
84.remove {
85  background: #e74c3c;
86  color: white;
87  border: none;
88  width: 28px;
89  height: 28px;
90  border-radius: 50%;
91  cursor: pointer;
92}
93
94/* Klasy TransitionGroup */
95.list-enter-active,
96.list-leave-active {
97  transition: all 0.4s ease;
98}
99.list-enter-from,
100.list-leave-to {
101  opacity: 0;
102  transform: translateX(30px);
103}
104/* Animacja przesunięcia */
105.list-move {
106  transition: transform 0.4s ease;
107}
108.list-leave-active {
109  position: absolute;
110}
111</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym <TransitionGroup> różni się od <Transition>?

  2. 2. Co robi atrybut tag='ul' w <TransitionGroup>?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Użyj <TransitionGroup> z v-for i name='list' do animacji listy.

  • Klikanie w kolejności

    Ułóż poprawną składnię TransitionGroup:

  • Edytor kodu

    Użyj klasy .list-move z transition na transform do animacji przesunięcia.

Przydatne artykuły