Lekcja 27

Dostępne animacje i prefers-reduced-motion

Ruch w interfejsie może pomagać zrozumieć zmianę, ale może też przeszkadzać użytkownikom wrażliwym na animacje. W tej lekcji nauczysz się projektować mikrointerakcje, które respektują ustawienia systemowe i zachowują informację bez zbędnego ruchu.

Cele lekcji

Po ukończeniu tej lekcji będziesz umieć:

  • wyjaśnić, czym jest prefers-reduced-motion,
  • napisać regułę @media (prefers-reduced-motion: reduce),
  • odróżnić ograniczenie ruchu od usunięcia całego feedbacku,
  • projektować alternatywy dla animacji wejścia, loaderów i efektów scrolla,
  • używać prostych tokenów ruchu w CSS,
  • testować, czy komponent nadal komunikuje stan po wyłączeniu animacji.

Kontekst praktyczny

Dostępny ruch jest ważny w dashboardach, formularzach, aplikacjach edukacyjnych, sklepach i panelach użytkownika. Największe ryzyko pojawia się przy efektach scrolla, parallaxie, dużych przesunięciach, nieskończonych loaderach i animacjach, które migają albo wymuszają śledzenie elementu wzrokiem.

W praktyce: użytkownik może mieć w systemie ustawioną preferencję ograniczenia ruchu. CSS pozwala ją wykryć bez JavaScriptu i podać spokojniejszą wersję tego samego komponentu.

Czym jest prefers-reduced-motion

prefers-reduced-motion to media feature, który informuje stronę, że użytkownik preferuje mniej animacji. Najczęściej obsługujesz wartość reduce.

@media (prefers-reduced-motion: reduce) {
  .animated-panel {
    animation: none;
    transition-duration: 0ms;
  }
}

Ta reguła powinna być świadomym wariantem komponentu, a nie przypadkowym wyłączeniem wszystkiego na końcu arkusza.

Reduce, nie zawsze remove

Ograniczenie ruchu nie oznacza usunięcia informacji. Gdy karta przestaje wysuwać się z boku, nadal może zmienić kolor obramowania, tło albo ikonę statusu. Feedback ma zostać, tylko bez dużego ruchu.

.notice {
  opacity: 0;
  transform: translateY(.75rem);
  animation: notice-enter 240ms ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .notice {
    opacity: 1;
    transform: none;
    animation: none;
    border-color: #22c55e;
  }
}

Transition i animation

Dla transition najczęściej skracasz czas trwania albo zostawiasz tylko zmianę koloru. Dla @keyframes zwykle wyłączasz sekwencję i ustawiasz stan końcowy wprost.

.card-link {
  transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
}

.card-link:hover {
  transform: translateY(-.2rem);
}

@media (prefers-reduced-motion: reduce) {
  .card-link {
    transition: border-color 120ms ease, background-color 120ms ease;
  }

  .card-link:hover {
    transform: none;
  }
}

Alternatywa bez ruchu

Wariant bez ruchu powinien nadal odpowiadać na pytanie: co się zmieniło? Używaj koloru, kontrastu, obramowania, tekstu statusu, ikony albo zmiany układu, która nie jest animowana.

.save-state {
  border: 1px solid #bbf7d0;
  background: #f0fdf4;
  color: #166534;
}

@media (prefers-reduced-motion: reduce) {
  .save-state::before {
    content: "Zapisano";
    font-weight: 800;
  }
}

Nie polegaj wyłącznie na ruchu. Jeśli animowany element znika albo zatrzymuje się, użytkownik nadal musi rozumieć stan.

Scroll i parallax

Efekty scrolla i parallax potrafią być bardziej obciążające niż małe mikrointerakcje. W trybie ograniczonego ruchu wyłącz płynne przewijanie i ruch zależny od scrolla.

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .parallax-layer {
    transform: none;
  }
}

Tokeny ruchu

W większym projekcie warto zapisać czas i dystans ruchu jako zmienne CSS. Dzięki temu tryb ograniczonego ruchu można kontrolować globalnie, bez przepisywania każdego komponentu.

:root {
  --motion-duration-fast: 160ms;
  --motion-distance-sm: .35rem;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-duration-fast: 1ms;
    --motion-distance-sm: 0;
  }
}

Demo dostępnego ruchu

Demo pokazuje ten sam komunikat w dwóch wariantach: z ruchem oraz z ograniczonym ruchem. Drugi wariant nie traci informacji, tylko rezygnuje z przesunięć i pulsowania.

Pełny ruch

Animowany feedback

Zmiany zapisane

Komunikat wchodzi przez krótki ruch i subtelny fade.

Reduced

Spokojny feedback

Zmiany zapisane

Stan jest widoczny przez kolor, obramowanie i tekst bez ruchu.

  • transformnone
  • animationnone
  • feedbackzachowany

Najczęstsze błędy początkujących

  • Wyłączanie animacji bez ustawienia stanu końcowego komponentu.
  • Usuwanie całego feedbacku, zamiast zamiany ruchu na kolor, tekst albo obramowanie.
  • Zostawianie scroll-behavior: smooth w trybie ograniczonego ruchu.
  • Animowanie dużych przesunięć, parallaxu i nieskończonych efektów bez alternatywy.
  • Ukrywanie błędów formularza albo statusów tylko przez animację.
  • Dodawanie jednej globalnej reguły, która przypadkowo niszczy potrzebne stany UI.

Mini zadanie

Przerób komponent powiadomienia z lekcji 26 na wersję dostępną.

  • Dodaj animację wejścia przez opacity i transform.
  • W @media (prefers-reduced-motion: reduce) wyłącz animację.
  • Ustaw stan końcowy ręcznie: opacity: 1 i transform: none.
  • Zachowaj informację o sukcesie przez kolor, ikonę albo tekst statusu.
  • Sprawdź, czy po ograniczeniu ruchu komponent nie znika i nie traci znaczenia.

Checklista

  • Wiem, po co istnieje prefers-reduced-motion.
  • Potrafię napisać wariant @media (prefers-reduced-motion: reduce).
  • Nie polegam wyłącznie na animacji jako informacji o stanie.
  • W trybie reduced motion ustawiam stan końcowy komponentu wprost.
  • Wyłączam albo ograniczam efekty scrolla, parallax i nieskończony ruch.
  • Projektuję feedback tak, żeby był czytelny także bez animacji.

Podsumowanie lekcji

Dostępna animacja ma cel, krótki czas trwania i spokojną alternatywę. prefers-reduced-motion pozwala uszanować ustawienia użytkownika, ale Twoim zadaniem jest zachować sens komponentu także wtedy, gdy ruch zostanie ograniczony.

Kolejny krok roadmapy to Moduł 6 i lekcja o nazewnictwie klas oraz BEM-like naming.

Przejdź do Lekcji 28