Reduce Motion — accessibility-funkcja systemów operacyjnych i przeglądarek, która wyłącza lub ogranicza animację interfejsu, aby zapobiec dyskomfortowi użytkowników z zaburzeniami przedsionkowymi. W iOS ustawienie znajduje się w Settings > Accessibility > Motion > Reduce Motion i wyłącza paralaksę, animację przejść między ekranami oraz efekty ikon. W Androidzie analogiczna opcja „Usuń animację” znajduje się w Ustawieniach > Dostępność. Według danych W3C WCAG 2.2, nadmierna animacja może powodować nudności, zawroty głowy i migrenę u 15–35% osób z zaburzeniami przedsionkowymi.
Najważniejsze
Reduce Motion — ustawienie systemowe nakazujące systemowi operacyjnemu, przeglądarce i aplikacjom minimalizowanie lub całkowite wyłączenie animacji, ruchu, skalowania i efektów paralaksy. Funkcja przeznaczona jest dla osób z zaburzeniami przedsionkowymi, u których ruchome elementy interfejsu powodują nudności, zawroty głowy, dezorientację i migrenę.
Układ przedsionkowy ucha wewnętrznego odpowiada za poczucie równowagi i orientację w przestrzeni. Gdy oczy widzą ruch na ekranie, a aparat przedsionkowy nie odczuwa odpowiadającego mu przemieszczenia ciała, powstaje konflikt sensoryczny. Ten konflikt wywołuje reakcję wegetatywną: nudności, zimny pot, zawroty głowy. Według danych Vestibular Disorders Association około 35% osób powyżej 40. roku życia odczuwa dyskomfort z powodu efektów ruchu w interfejsach.
Kluczowa różnica Reduce Motion od prostego wyłączenia animacji — poziom systemowy. Włączone ustawienie przekazywane jest wszystkim aplikacjom i stronom internetowym przez API systemu operacyjnego lub funkcję medialną CSS prefers-reduced-motion. Deweloper nie musi zgadywać, czy użytkownik chce animacji — system informuje o tym. Jest to część podejścia progressive enhancement: animacja to ulepszenie, a nie obowiązkowy element.
Reduce Motion wpływa na efekty paralaksy (przesunięcie tła przy pochyleniu urządzenia), animację przejść między ekranami (push, slide, fade), skalowanie ikon przy kliknięciu, efekty wiadomości (bańki SMS), animację ładowania (spinner, skeleton, progress bar animation), paralaksę w grach oraz efekty parallax scrolling w sieci. Obrazy statyczne i wideo nie są naruszane.
| Typ animacji | iOS Reduce Motion | Android Remove Animation | CSS prefers-reduced-motion |
|---|---|---|---|
| Paralaksa tapety | Wyłącza | Wyłącza | N/D |
| Przejścia między ekranami | Zastępuje na fade | Wyłącza | CSS transitions |
| Animacja ikon | Wyłącza | Nie wpływa | CSS animations |
| Spinner ładowania | Nie wpływa | Wyłącza | CSS animations |
| Skeleton screens | Nie wpływa | Zastępuje na static | CSS animations |
Główna grupa docelowa Reduce Motion — osoby z zaburzeniami przedsionkowymi (choroba Meniere'a, zapalenie błędnika, BPPV), migreną (zwłaszcza z aurą), kinetozą (choroba lokomocyjna) i niektórymi formami padaczki. Funkcja jest również przydatna dla starszych użytkowników — zmiany wieku w aparacie przedsionkowym zwiększają wrażliwość na ruch, oraz dla użytkowników z ADHD — ruchome elementy rozpraszają podczas czytania.
W iOS Reduce Motion znajduje się w Settings > Accessibility > Motion i zawiera trzy opcje: Reduce Motion (główny przełącznik), Auto-Play Message Effects (autoodtwarzanie efektów iMessage) oraz Auto-Play Video Previews (autoodtwarzanie wideo w App Store). Reduce Motion jest najpotężniejszy: wyłącza paralaksę tapety, animację otwierania aplikacji (zoom), przejścia między ekranami (push zastępowany cross-fade) oraz efekty ikon na ekranie głównym.
Technicznie iOS implementuje Reduce Motion na poziomie UIKit przez UIView animations i UIViewControllerAnimatedTransitioning. Przy włączonym ustawieniu UIView.animate o czasie trwania > 0 jest skracany do 0,01 sek, a UIViewController.transitionDuration zwraca 0. Dla paralaksy iOS wyłącza UIInterpolatingMotionEffect na UIWindow. Deweloper może sprawdzić stan przez UIAccessibility.isReduceMotionEnabled.
Deweloper może dostosować animację w iOS do Reduce Motion: zastąpić złożone przejścia efektami fade, wyłączyć paralaksę w niestandardowych komponentach UI oraz skrócić czas trwania animacji. SwiftUI udostępnia specjalny modyfikator .animation(nil) przy aktywnym reduceMotion.
import UIKit
class MotionManager {
// Sprawdzanie stanu Reduce Motion
static var isReduceMotionEnabled: Bool {
return UIAccessibility.isReduceMotionEnabled
}
// Bezpieczna animacja z uwzględnieniem Reduce Motion
static func animate(
duration: TimeInterval,
delay: TimeInterval = 0,
options: UIView.AnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
) {
let adjustedDuration = isReduceMotionEnabled
? 0.01
: max(duration, 0.01)
UIView.animate(
withDuration: adjustedDuration,
delay: delay,
options: options,
animations: animations,
completion: completion
)
}
}
// SwiftUI — modyfikator dla Reduce Motion
import SwiftUI
struct MotionAwareView: View {
@Environment(\.accessibilityReduceMotion)
private var reduceMotion
@State private var isAnimating = false
var body: some View {
Circle()
.scaleEffect(isAnimating ? 1.2 : 1.0)
.animation(
reduceMotion
? nil
: .easeInOut(duration: 1.0).repeatForever(),
value: isAnimating
)
.onAppear { isAnimating = true }
}
}
// Subskrypcja zmiany Reduce Motion
NotificationCenter.default.addObserver(
self,
selector: #selector(motionStatusChanged),
name: UIAccessibility.reduceMotionStatusDidChangeNotification,
object: nil
)Klasa MotionManager sprawdza isReduceMotionEnabled i dostosowuje czas trwania animacji: przy aktywnym Reduce Motion czas skraca się do 0,01 sek (faktycznie natychmiast). Modyfikator SwiftUI .animation(nil) całkowicie wyłącza animację przy reduceMotion == true. Subskrypcja reduceMotionStatusDidChangeNotification umożliwia dynamiczne przełączanie animacji bez przeładowywania aplikacji.
Oddzielna opcja iOS — Auto-Play Message Effects — steruje odtwarzaniem efektów w iMessage: animacją balonów, konfetti, laserami i fajerwerkami. Przy włączonym Reduce Motion efekty iMessage są automatycznie wyłączane. Deweloper może sprawdzić tę opcję przez UIAccessibility.isReduceMotionEnabled — oddzielnego API dla Message Effects nie ma, są one powiązane z ogólnym ustawieniem.
W Androidzie ustawienie „Usuń animację” znajduje się w dwóch miejscach: Ustawienia > Dostępność > Usuń animację (dla ustawień dostępnościowych) oraz Opcje programisty > Skala animacji (dla programistów). Wersja dostępnościowa wyłącza wszystkie animacje systemowe, w tym przejścia między ekranami, animację otwierania aplikacji i efekty ładowania. Opcje programisty umożliwiają zmniejszenie skali do 0,5x, 0,1x lub całkowite wyłączenie.
Technicznie Android implementuje wyłączanie animacji przez właściwości systemowe: window_animation_scale, transition_animation_scale i animator_duration_scale. Przy ustawieniu dostępnościowym wszystkie trzy otrzymują wartość 0. Systemowy Launcher i aplikacje systemowe sprawdzają te wartości. Dla aplikacji zewnętrznych Android zaleca używanie Jetpack Compose i View system, które automatycznie odczytują ustawienie systemowe przez AnimationScaleObserver.
Deweloper może sprawdzić stan animacji w Androidzie przez Settings.Global i dostosować interfejs. Jetpack Compose udostępnia AnimationScaleObserver do automatycznego dostosowania. Kluczowa wartość — animator_duration_scale: jeśli 0, animacja jest całkowicie wyłączona.
import android.content.Context
import android.provider.Settings
import android.database.ContentObserver
import android.os.Handler
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.runtime.getValue
class AndroidMotionHelper {
// Sprawdzanie wyłączenia animacji
fun isAnimationDisabled(context: Context): Boolean {
return try {
Settings.Global.getFloat(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE
) == 0f
} catch (e: Settings.SettingNotFoundException) {
false
}
}
// Obserwacja zmiany animacji (ContentObserver)
fun observeAnimationScale(
context: Context,
onChange: (Boolean) -> Unit
): ContentObserver {
val observer = object : ContentObserver(Handler()) {
override fun onChange(selfChange: Boolean) {
onChange(isAnimationDisabled(context))
}
}
context.contentResolver.registerContentObserver(
Settings.Global.getUriFor(
Settings.Global.ANIMATOR_DURATION_SCALE
),
false,
observer
)
return observer
}
}
// Jetpack Compose — animacja z uwzględnieniem ustawień
@Composable
fun MotionAwareAnimation(enabled: Boolean) {
val context = androidx.compose.ui.platform.LocalContext.current
val noMotion = AndroidMotionHelper().isAnimationDisabled(context)
val scale by animateFloatAsState(
targetValue = if (enabled) 1f else 0f,
animationSpec = if (noMotion) {
androidx.compose.animation.core.tween(durationMillis = 0)
} else {
androidx.compose.animation.core.spring()
},
label = "scale"
)
// Interfejs z animacją
}
Klasa AndroidMotionHelper sprawdza ANIMATOR_DURATION_SCALE przez Settings.Global, udostępnia ContentObserver do śledzenia zmian i umożliwia animacjom Jetpack Compose dostosowanie się: przy wyłączonej animacji durationMillis = 0 (natychmiastowe zastosowanie). Dla View-system używaj ViewPropertyAnimator.withLayer() i sprawdź isAnimationDisabled() przed uruchomieniem animacji.
Ustawienie dostępnościowe „Usuń animację” w Androidzie — jedyny sposób dla zwykłego użytkownika na wyłączenie animacji. Opcje programisty są przeznaczone tylko dla programistów i mogą być ukryte. Ustawienie dostępnościowe gwarantowanie przekazywane jest wszystkim aplikacjom przez systemowe API. Deweloper powinien kierować się na AccessibilityManager, a nie na Opcje programisty.
Funkcja medialna CSS prefers-reduced-motion umożliwia twórcom stron wyłączanie lub zastępowanie animacji, gdy użytkownik włączył Reduce Motion w systemie operacyjnym. Przeglądarka przekazuje wartość reduce przy aktywnej konfiguracji systemowej, no-preference — jeśli ustawienie nie zostało zmienione. Obsługiwana przez wszystkie nowoczesne przeglądarki od 2020 roku: Chrome 74+, Safari 10.3+, Firefox 64+, Edge 79+.
Zalecenie MDN i W3C — zawsze ustawiać prefers-reduced-motion: reduce wraz z animacjami. Użytkownik, który włączył Reduce Motion, nie powinien widzieć pulsacji, paralaksy, autoodtwarzania karuzel ani ładowania skeleton. Zamiast animacji można pokazywać statyczną treść lub używać CSS transition z minimalnym czasem trwania. Poniżej znajduje się przykład pełnej adaptacji.
Przy prefers-reduced-motion: reduce strona internetowa powinna: wyłączyć animacje CSS (@keyframes), zastąpić CSS transitions natychmiastowymi zmianami, zatrzymać animacje JavaScript (requestAnimationFrame), wyłączyć parallax scrolling, zatrzymać karuzele i suwaki, wyłączyć efekty skeleton i zastąpić spinner statycznym wskaźnikiem.
/* Podstawowe animacje (no-preference) */
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e0e0e0;
border-top-color: #0066CC;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e8e8e8 37%,
#f0f0f0 63%
);
background-size: 400% 100%;
animation: shimmer 1.4s ease infinite;
}
.parallax-bg {
transform: translateZ(-1px) scale(1.5);
transition: transform 0.3s ease-out;
}
/* Reduce Motion: wyłączamy wszystko */
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
border-top-color: #0066CC;
}
.skeleton {
animation: none;
background: #f0f0f0;
}
.parallax-bg {
transform: none;
transition: none;
}
.carousel {
scroll-behavior: auto;
overflow-x: auto;
}
.fade-in {
opacity: 1;
transform: none;
}
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}W przykładzie CSS użyto globalnego resetu animacji przy prefers-reduced-motion: reduce: wszystkie animacje i przejścia otrzymują duration 0.01ms i iteration-count 1. Osobno nadpisano spinner (statyczna ramka), skeleton (szare tło bez migotania), paralaksę (wyłączona) i karuzelę (scroll-behavior: auto zamiast smooth). Uniwersalny selektor * — zabezpieczenie dla wszystkich elementów.
Dla JavaScript dostępny jest matchMedia('(prefers-reduced-motion: reduce)').matches. Jeśli true — nie uruchamiaj animacji przez requestAnimationFrame, setInterval lub Web Animations API. Przykład: suwak przy reduceMotion nie kręci się automatycznie, tylko po przyciskach. Biblioteki GSAP, Framer Motion i Anime.js obsługują to sprawdzenie przez swoje opcje dostępnościowe.
WCAG 2.2 (2023) wprowadził nowe kryterium 2.3.3 Animation from Interactions (poziom AAA): jeśli animacja uruchamia się od interakcji użytkownika (kliknięcie, przewijanie, najechanie), musi być możliwa do wyłączenia lub nie przekraczać 5 sekund. Animacja niezwiązana z interakcją (spinner ładowania, pasek postępu) nie podlega temu kryterium. Dla poziomu AA wystarczy, aby animacja nie była jedynym sposobem przekazywania informacji.
Kryterium 2.3.3 zostało dodane po licznych skargach na chorobę lokomocyjną od parallax scrollingu i animacji „pociągnij-odśwież”. W3C zaleca wszystkim stronom i aplikacjom: nie animować elementów dłużej niż 5 sekund, zapewnić przełącznik wyłączania animacji (przez panel sterowania lub prefers-reduced-motion), nie używać pulsujących i migających elementów (niebezpieczne dla padaczki).
Dla zgodności z WCAG 2.2 deweloper powinien: sprawdzić prefers-reduced-motion i wyłączyć animację przy aktywnym ustawieniu, ustawić maksymalny czas trwania animacji 5 sekund (jeśli animacja nie może być wyłączona), zapewnić przełącznik UI animacji w ustawieniach aplikacji, nie używać efektów paralaksy jako jedynego sposobu nawigacji oraz testować z włączonym Reduce Motion we wszystkich przeglądarkach i na wszystkich urządzeniach.
Do testowania zachowania aplikacji z Reduce Motion używaj: iOS Simulator (włącz Reduce Motion w Settings > Accessibility), Android Emulator (włącz „Usuń animację”), Chrome DevTools (Rendering > Emulate CSS media feature prefers-reduced-motion), inspektor sieciowy Safari (włącz Reduce Motion w macOS System Preferences > Accessibility > Display). Sprawdź, czy treść pozostaje dostępna i czytelna bez animacji.
Często zadawane pytania
Reduce Motion — funkcja dostępnościowa systemu operacyjnego, wyłączająca animację interfejsu (paralaksę, przejścia, skalowanie ikon) dla osób z zaburzeniami przedsionkowymi. Włącza się w Settings > Accessibility. System przekazuje sygnał aplikacjom przez UIAccessibility (iOS), Settings.Global (Android) lub prefers-reduced-motion (CSS), a deweloper może dostosować animację.
Na iPhonie Reduce Motion włącza się w Settings > Accessibility > Motion > Reduce Motion. Po włączeniu wyłączane są: paralaksa tapety, efekty iMessage, animacja otwierania aplikacji i przejścia między ekranami (zastępowane cross-fade). Systemowe animacje UIKit nie są odtwarzane. Ustawienie stosuje się do wszystkich aplikacji bez przeładowywania urządzenia.
W Androidzie animację wyłącza się na dwa sposoby: Ustawienia > Dostępność > Usuń animację (zalecane dla użytkowników) lub Opcje programisty > Skala animacji — wyłącz. Sposób dostępnościowy gwarantuje, że wszystkie aplikacje otrzymają sygnał. Opcje programisty wpływają tylko na animacje systemowe i mogą być ukryte na niektórych urządzeniach.
Funkcja medialna CSS prefers-reduced-motion: reduce umożliwia wyłączenie animacji na poziomie stylów. Zaleca się ustawienie uniwersalnego resetu: animation-duration: 0.01ms, transition-duration: 0.01ms, animation-iteration-count: 1 dla wszystkich elementów. Osobno nadpisać konkretne komponenty (spinner, skeleton, paralaksę). JavaScript sprawdza przez matchMedia('(prefers-reduced-motion: reduce)').
WCAG 2.2 kryterium 2.3.3 (AAA) wymaga, aby animacja od interakcji użytkownika była możliwa do wyłączenia lub nie przekraczała 5 sekund. Dla AA wystarczy, aby animacja nie była jedynym sposobem przekazywania informacji. Zaleca się zawsze sprawdzać prefers-reduced-motion i zapewnić przełącznik UI animacji w ustawieniach aplikacji.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również