Kurs Vue.js · Moduł 13: Przejścia i animacje
Projekt: System animacji NOVA LAB
W tej lekcji4
Czas połączyć wszystkie techniki animacji w spójny system interfejsu stacji kosmicznej! W tym projekcie stworzysz kompletny dashboard NOVA LAB, który wykorzystuje <Transition>, <TransitionGroup>, named transitions, mode out-in, staggered animations, appear i accessibility - wszystko co poznałeś w tym module.
Podsumowanie modułu
W tym module nauczyłeś się:
<Transition>- animowanie wejścia/wyjścia pojedynczych elementów z 6 klasami CSS- Named Transitions - definiowanie wielu typów animacji z atrybutem
name - Tryby przejść (mode) - kontrola kolejności animacji przy przełączaniu widoków (
out-in,in-out) - JavaScript Animation Hooks - pełna kontrola nad animacjami przez
@enter,@leavei callbackdone() <TransitionGroup>- animowanie list zv-for, klasa-movedla przesuwania elementów- Integracja z bibliotekami - custom class names dla Animate.css, GSAP przez JS hooks
- Route transitions - płynne przejścia między stronami z
<RouterView> - Appear - animacja przy pierwszym renderowaniu, staggered loading
- Accessibility - respektowanie
prefers-reduced-motion
Wymagania projektu
Zbuduj Animowany Dashboard Stacji NOVA LAB z następującymi elementami:
1. Panel kontrolny z animacją fade
Główny panel, który można pokazać/ukryć przyciskiem. Użyj <Transition name="fade"> z v-if.
2. Przełączanie widoków z mode out-in
Wewnątrz panelu zaimplementuj zakładki (np. "Status" i "Alerts"). Przełączanie między nimi powinno używać <Transition name="slide" mode="out-in"> z dynamicznym :key.
3. Lista alertów z TransitionGroup
W zakładce "Alerts" wyświetlaj listę alertów z możliwością dodawania i usuwania. Użyj <TransitionGroup name="list"> z klasami enter, leave i move.
4. Staggered loading elementów przy mount
Lista modułów stacji powinna pojawiać się kaskadowo przy załadowaniu aplikacji. Użyj <TransitionGroup appear :css="false"> z JS hookami i opóźnieniem na podstawie indeksu.
5. Accessibility
Dodaj obsługę prefers-reduced-motion - użytkownicy, którzy nie chcą animacji, powinni widzieć natychmiastowe zmiany bez przejść.
Architektura
1App.vue
2├── Toggle Button (show/hide panel)
3├── Panel kontrolny (Transition name="fade")
4│ ├── Tabs (status / alerts)
5│ │ └── Transition name="slide" mode="out-in"
6│ │ ├── Status view
7│ │ │ └── System metrics display
8│ │ └── Alerts view
9│ │ ├── Add alert button
10│ │ └── TransitionGroup name="list"
11│ │ └── Alert items (add/remove)
12│ └── System modules list (TransitionGroup appear :css="false")
13│ └── Staggered items (delay based on index)Kluczowe wzorce do zastosowania
- Zagnieżdżone Transition - fade na panelu, slide na zakładkach, list na alertach
- Różne techniki w jednym projekcie - CSS transitions (fade, slide), CSS move (lista), JS hooks (stagger)
- TransitionGroup z move - dodawanie alertu na początku listy powoduje płynne przesunięcie reszty
- Appear ze stagger - kaskadowe ładowanie modułów daje efekt "włączania systemów stacji"
- Callback done() - w JS hookach pamiętaj o wywołaniu
done()po zakończeniu animacji
Spróbuj rozbudować projekt o dodatkowe efekty - np. animację bounce dla krytycznych alertów lub efekt pulse dla aktywnych modułów!
Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const showPanel = ref(false)
5const alerts = ref([
6 { id: 1, text: 'System podtrzymywania życia: OK', type: 'success' },
7 { id: 2, text: 'Poziom tlenu: 21%', type: 'info' }
8])
9const currentView = ref('status')
10let alertId = 3
11
12function addAlert() {
13 const texts = [
14 'Wykryto anomalię temperatury',
15 'Wydajność paneli słonecznych: 87%',
16 'Silny sygnał przekaźnika łączności',
17 'Odzysk wody wymaga serwisu'
18 ]
19 alerts.value.push({
20 id: alertId++,
21 text: texts[Math.floor(Math.random() * texts.length)],
22 type: Math.random() > 0.5 ? 'info' : 'success'
23 })
24}
25
26function removeAlert(id) {
27 alerts.value = alerts.value.filter(a => a.id !== id)
28}
29</script>
30
31<template>
32 <div class="nova-lab">
33 <h1>NOVA LAB - System Animacji (Projekt)</h1>
34
35 <!-- Przełączanie panelu z przejściem fade -->
36 <button @click="showPanel = !showPanel" class="btn primary">
37 {{ showPanel ? 'Zamknij' : 'Otwórz' }} panel kontrolny
38 </button>
39
40 <Transition name="fade">
41 <div v-if="showPanel" class="control-panel">
42 <h2>Panel Kontrolny</h2>
43
44 <!-- Przełącznik widoków ze slide + out-in -->
45 <div class="tabs">
46 <button @click="currentView = 'status'" :class="{active: currentView === 'status'}">Status</button>
47 <button @click="currentView = 'alerts'" :class="{active: currentView === 'alerts'}">Alerty</button>
48 </div>
49
50 <Transition name="slide" mode="out-in">
51 <div v-if="currentView === 'status'" key="status" class="view">
52 <p>Wszystkie systemy operacyjne</p>
53 </div>
54 <div v-else key="alerts" class="view">
55 <button @click="addAlert" class="btn small">+ Alert</button>
56 <TransitionGroup name="list" tag="div">
57 <div v-for="alert in alerts" :key="alert.id" :class="['alert', alert.type]">
58 {{ alert.text }}
59 <button @click="removeAlert(alert.id)" class="close">×</button>
60 </div>
61 </TransitionGroup>
62 </div>
63 </Transition>
64 </div>
65 </Transition>
66 </div>
67</template>
68
69<style scoped>
70.nova-lab {
71 background: #0a0e27;
72 color: #00ff88;
73 min-height: 100vh;
74 padding: 20px;
75 font-family: monospace;
76}
77h1, h2 { color: #00ff88; }
78.btn { border: none; padding: 10px 16px; border-radius: 8px; cursor: pointer; color: white; }
79.primary { background: #00b4d8; margin-bottom: 15px; }
80.small { background: #9b59b6; font-size: 12px; padding: 6px 12px; margin-bottom: 10px; }
81
82.control-panel {
83 background: rgba(0, 180, 216, 0.05);
84 border: 2px solid #00b4d8;
85 padding: 20px;
86 border-radius: 12px;
87}
88.tabs { display: flex; gap: 8px; margin: 10px 0; }
89.tabs button {
90 background: rgba(0,180,216,0.1);
91 color: #00b4d8;
92 border: 1px solid #00b4d8;
93 padding: 6px 14px;
94 border-radius: 6px;
95 cursor: pointer;
96}
97.tabs button.active { background: #00b4d8; color: white; }
98
99.view { min-height: 100px; }
100.alert {
101 display: flex;
102 justify-content: space-between;
103 align-items: center;
104 padding: 10px 14px;
105 margin: 6px 0;
106 border-radius: 6px;
107}
108.alert.success { background: rgba(0,255,136,0.1); border: 1px solid #00ff88; }
109.alert.info { background: rgba(0,180,216,0.1); border: 1px solid #00b4d8; }
110.close { background: transparent; border: none; color: #e74c3c; cursor: pointer; font-size: 16px; }
111
112/* Fade */
113.fade-enter-active, .fade-leave-active { transition: opacity 0.4s ease; }
114.fade-enter-from, .fade-leave-to { opacity: 0; }
115
116/* Slide */
117.slide-enter-active, .slide-leave-active { transition: all 0.3s ease; }
118.slide-enter-from { opacity: 0; transform: translateX(20px); }
119.slide-leave-to { opacity: 0; transform: translateX(-20px); }
120
121/* Lista */
122.list-enter-active, .list-leave-active { transition: all 0.3s ease; }
123.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }
124.list-move { transition: transform 0.3s ease; }
125.list-leave-active { position: absolute; }
126</style>Widzisz błąd w tej lekcji?