Kurs Vue.js · Moduł 13: Przejścia i animacje
Zaawansowane wzorce animacji
W tej lekcji5
W zaawansowanych systemach NOVA LAB potrzebujemy wyrafinowanych efektów - kaskadowe ładowanie elementów dashboardu, animacja przy pierwszym wyświetleniu strony, precyzyjna kontrola czasów trwania i szacunek dla preferencji użytkowników dotyczących ruchu. Poznaj wzorce, które nadają interfejsom profesjonalny charakter.
Appear - animacja przy mount
Domyślnie <Transition> animuje element tylko przy przełączeniu v-if/v-show. Atrybut appear powoduje, że animacja uruchomi się również przy pierwszym renderowaniu (mount) komponentu:
1<Transition appear name="fade">
2 <div>Ten element animuje się przy załadowaniu strony</div>
3</Transition>To idealne rozwiązanie dla elementów widocznych od razu po załadowaniu - logo stacji, nagłówek dashboardu, panel statusu. Bez appear te elementy po prostu "byłyby tam" bez żadnej animacji wejścia.
Możesz użyć specjalnych klas CSS dla animacji appear (niezależnych od normalnych klas enter):
1<Transition
2 appear
3 appear-from-class="appear-start"
4 appear-active-class="appear-running"
5 appear-to-class="appear-end"
6>
7 <div>Custom appear animation</div>
8</Transition>Lub z bibliotekami:
1<Transition
2 appear
3 appear-active-class="animate__animated animate__fadeInDown"
4>
5 <header>NOVA LAB Control Center</header>
6</Transition>Staggered list animation
Jeden z najbardziej efektownych wzorców - kaskadowe pojawianie się elementów listy, gdzie każdy kolejny element pojawia się z rosnącym opóźnieniem. Wygląda jak wyświetlanie danych na holograficznym dashboardzie NOVA LAB, element po elemencie:
1<TransitionGroup
2 appear
3 :css="false"
4 @before-enter="onBeforeEnter"
5 @enter="onEnter"
6 tag="div"
7>
8 <div v-for="(item, index) in items" :key="item.id" :data-index="index">
9 {{ item.name }}
10 </div>
11</TransitionGroup>Kluczem jest atrybut :data-index - przekazuje indeks elementu do hooka JS, który oblicza opóźnienie:
1function onBeforeEnter(el) {
2 el.style.opacity = 0
3 el.style.transform = 'translateY(20px)'
4}
5
6function onEnter(el, done) {
7 // Każdy kolejny element ma o 150ms większe opóźnienie
8 const delay = el.dataset.index * 150
9
10 setTimeout(() => {
11 el.style.transition = 'all 0.5s ease'
12 el.style.opacity = 1
13 el.style.transform = 'translateY(0)'
14 el.addEventListener('transitionend', done, { once: true })
15 }, delay)
16}Efekt: element 0 pojawia się natychmiast, element 1 po 150ms, element 2 po 300ms itd. Dla 10 elementów cała animacja trwa 1.5s + 0.5s (czas samej animacji) = ~2s.
Wskazówka: Ogranicz opóźnienie do max ~1s, aby użytkownik nie czekał zbyt długo na ostatnie elementy:
1const delay = Math.min(el.dataset.index * 100, 1000)Explicit duration
Gdy element ma zarówno CSS transition jak i animation, lub gdy zagnieżdżone elementy mają różne czasy animacji, Vue może nie wykryć prawidłowego momentu zakończenia. Atrybut :duration pozwala jawnie określić czas trwania w milisekundach:
1<!-- Jednolity czas dla wejścia i wyjścia -->
2<Transition :duration="500">...</Transition>
3
4<!-- Różne czasy dla wejścia i wyjścia -->
5<Transition :duration="{ enter: 300, leave: 500 }">...</Transition>Jest to szczególnie przydatne gdy:
- Animujesz zagnieżdżone elementy z różnymi czasami
- Łączysz CSS transition i animation na tym samym elemencie
- Używasz bibliotek zewnętrznych, których czas trwania nie jest automatycznie wykrywany
Accessibility - prefers-reduced-motion
Szanuj ustawienia użytkownika dotyczące ruchu. Osoby z zaburzeniami przedsionkowymi lub epilepsją mogą mieć włączoną preferencję prefers-reduced-motion. Twój CSS powinien to respektować:
1/* Normalne animacje */
2.fade-enter-active,
3.fade-leave-active {
4 transition: opacity 0.3s ease, transform 0.3s ease;
5}
6
7.fade-enter-from,
8.fade-leave-to {
9 opacity: 0;
10 transform: translateY(10px);
11}
12
13/* Wyłącz animacje dla użytkowników, którzy ich nie chcą */
14@media (prefers-reduced-motion: reduce) {
15 .fade-enter-active,
16 .fade-leave-active {
17 transition: none;
18 }
19
20 .fade-enter-from,
21 .fade-leave-to {
22 opacity: 1;
23 transform: none;
24 }
25}Możesz też obsłużyć to w JavaScript:
1const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
2
3function onEnter(el, done) {
4 if (prefersReducedMotion) {
5 done() // Pomiń animację
6 return
7 }
8 // Normalna animacja...
9}Best practices wydajności
Animuj tylko
transformiopacity- te właściwości są akcelerowane przez GPU i nie powodują reflow/repaint. Unikaj animowaniawidth,height,top,left,margin,padding.Używaj
will-changedla ciężkich animacji - informuje przeglądarkę o nadchodzącej animacji, pozwalając na optymalizację:
1.heavy-animation-enter-active {
2 will-change: transform, opacity;
3 transition: all 0.5s ease;
4}Preferuj CSS nad JS - CSS animations są optymalizowane przez przeglądarkę i działają na osobnym wątku. Używaj JS hooków tylko gdy potrzebujesz dynamicznych wartości.
Krótkie czasy trwania - animacje UI powinny trwać 200-500ms. Dłuższe animacje spowalniają interakcję i irytują użytkowników.
Testuj na urządzeniach mobilnych - animacje, które działają płynnie na desktopie, mogą lagować na starszych telefonach.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Appear - animacja przy montowaniu
5const items = ref([
6 { id: 1, name: 'Podtrzymanie życia', delay: 0 },
7 { id: 2, name: 'Nawigacja', delay: 100 },
8 { id: 3, name: 'Komunikacja', delay: 200 },
9 { id: 4, name: 'Sieć energetyczna', delay: 300 },
10 { id: 5, name: 'Laboratorium naukowe', delay: 400 }
11])
12
13function onBeforeEnter(el) {
14 el.style.opacity = 0
15 el.style.transform = 'translateY(20px)'
16}
17
18function onEnter(el, done) {
19 const delay = el.dataset.index * 150
20 setTimeout(() => {
21 el.style.transition = 'all 0.5s ease'
22 el.style.opacity = 1
23 el.style.transform = 'translateY(0)'
24 el.addEventListener('transitionend', done, { once: true })
25 }, delay)
26}
27</script>
28
29<template>
30 <div class="nova-lab">
31 <h1>Lista kaskadowa + appear</h1>
32
33 <TransitionGroup
34 appear
35 :css="false"
36 @before-enter="onBeforeEnter"
37 @enter="onEnter"
38 tag="div"
39 class="systems"
40 >
41 <div
42 v-for="(item, index) in items"
43 :key="item.id"
44 :data-index="index"
45 class="system-card"
46 >
47 <span class="dot">●</span>
48 {{ item.name }}
49 </div>
50 </TransitionGroup>
51 </div>
52</template>
53
54<style scoped>
55.nova-lab {
56 background: #0a0e27;
57 color: #00ff88;
58 min-height: 100vh;
59 padding: 20px;
60 font-family: monospace;
61}
62h1 { color: #00ff88; }
63.systems { margin-top: 20px; }
64.system-card {
65 display: flex;
66 align-items: center;
67 gap: 12px;
68 background: rgba(0, 180, 216, 0.1);
69 border: 1px solid #00b4d8;
70 padding: 14px 20px;
71 margin: 8px 0;
72 border-radius: 8px;
73 font-size: 14px;
74}
75.dot { color: #00ff88; }
76</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. Co robi atrybut appear w <Transition>?
2. Jak ustawić jawny czas trwania animacji w <Transition>?
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Ułóż kroki implementacji staggered (opóźnionej) animacji listy:
- Edytor kodu
Użyj data-index i gsap/setTimeout do staggered animation w TransitionGroup.
- Układanie w poziomie
Ułóż atrybut appear z custom class:
- Klikanie w kolejności
Ułóż kod przekazania indeksu do elementu listy:
- Układanie w pionie
Ułóż techniki animacji od najprostszej do najbardziej zaawansowanej:
- Układanie w poziomie
Ułóż składnię duration z różnymi czasami enter/leave:
- Klikanie w kolejności
Ułóż hooki JavaScript w kolejności wywołania (animacja wyjścia):
- Klikanie w kolejności
Ułóż deklarację CSS transition shorthand:
- Układanie w pionie
Ułóż 6 klas CSS dodawanych przez <Transition> w kolejności chronologicznej: