Спроектируй стратегию prefers-reduced-motion для проекта на стеке: {{stack}}. Цель — соответствие WCAG 2.3.3, нулевой вестибулярный риск, сохранение смысла интерфейса.
1. Категоризация анимаций
Раздели всю анимацию проекта на три бакета:
| Бакет | Что входит | Действие при reduced-motion |
|---|---|---|
| A. Декоративная | parallax, auto-play видео, плавающие частицы, hero-петли, marquee | Полностью отключить |
| B. Сюжетная | slide-in карточек, page transitions, modal scale, accordion expand | Заменить на fade 100-150ms или мгновенный показ |
| C. Сигнальная | focus ring, loading spinner, прогресс-бар, toast appearance, status change | Сохранить, но упростить (короткий fade, без движения по оси) |
Правило: если анимация передаёт смысл (что-то загружается, статус изменился) — её нельзя выключать целиком. Иначе пользователь потеряет информацию.
2. Технический паттерн
/* База: motion-friendly defaults */
.card { transition: transform 250ms var(--ease-out), opacity 250ms; }
@media (prefers-reduced-motion: reduce) {
/* A. Декоративная — полный off */
.parallax, .marquee, .particle-bg { animation: none !important; transform: none !important; }
/* B. Сюжетная — fade вместо slide */
.card { transition: opacity 150ms ease; transform: none !important; }
.modal-enter { transform: none; }
/* C. Сигнальная — короче, без движения */
.toast { transition: opacity 120ms ease; transform: none; }
.spinner { animation-duration: 1.2s; } /* НЕ убираем — это статус */
}
Глобальный kill-switch (* { animation-duration: 0.01ms !important }) — запрещён: он ломает сигнальные индикаторы.
3. JS-стек (Framer Motion / GSAP)
import { useReducedMotion } from "framer-motion";
const shouldReduce = useReducedMotion();
const variants = shouldReduce
? { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { duration: 0.15 } } }
: { hidden: { opacity: 0, y: 16 }, visible: { opacity: 1, y: 0, transition: { duration: 0.3 } } };
GSAP: проверяй window.matchMedia('(prefers-reduced-motion: reduce)').matches и заменяй .from(el, {y: 40}) на .set(el, {autoAlpha: 1}).
4. Что НЕ должно быть motion-зависимым (никогда)
- Loading indicators — статус процесса. Замени spin на pulsing dot или текст «Загрузка…».
- Focus indicators — обязаны быть видны сразу, без transition opacity.
- Status changes (success/error) — цвет + иконка + ARIA-live; анимация — бонус.
- Hover affordance — кнопка должна выглядеть кликабельной без анимации (контраст, форма).
- Form validation — ошибка показывается мгновенно, не через slide-in.
- Navigation state (active tab, current page) — статичные стили обязаны работать без анимации.
5. Чек-лист тестирования
- macOS: System Settings → Accessibility → Display → Reduce motion.
- iOS: Settings → Accessibility → Motion → Reduce Motion.
- Windows 11: Settings → Accessibility → Visual effects → Animation effects → Off.
- Chrome DevTools: Rendering → Emulate CSS media feature prefers-reduced-motion: reduce.
- Пройди ключевые сценарии: понятен ли статус? Видны ли переходы между экранами? Не «телепортируется» ли модалка?
- Lighthouse Accessibility audit + axe DevTools — проверь, что нет нарушений 2.3.3 Three Flashes.
6. Формат вывода
## Аудит motion-проекта
| Компонент | Бакет (A/B/C) | Что делает сейчас | Что делает при reduced |
|---|---|---|---|
| HeroParallax | A | translateY на scroll | display none на mobile, off |
| CardGrid | B | stagger slide-up | fade 150ms одновременно |
| Spinner | C | rotate infinite | pulse opacity 0.4↔1, 1.2s |
## Изменения в коде
[файлы + диффы]
## Чек-лист QA
- [ ] Все 6 пунктов из секции 5 пройдены
Анти-паттерны
- ❌ Глобальный
* { transition: none !important }— ломает focus ring, ARIA-визуализацию, spinner. - ❌ Полное отключение loading-анимаций — пользователь думает, что зависло.
- ❌ Reduced-motion = «выключить всё красивое» — это про вестибулярный риск (движение по оси), не про эстетику.
- ❌ Тестирование только в Chrome DevTools без реального OS-флага — поведение медиа-запроса в эмуляции иногда расходится с системным.
- ❌ Замена slide на ещё более резкий «snap» — тоже триггерит вестибулярку. Используй fade.
- ❌ Игнор JS-анимаций (GSAP, Framer Motion, Lottie) при наличии CSS-fallback — JS перезатирает CSS.
- ❌ Lottie-анимация без
prefers-reduced-motionfallback — самые тяжёлые для людей с вестибулярными нарушениями.
Аудит доступности по WCAG 2.2 AA
Проверка контраста, клавиатурной навигации, скринридеров, фокус-индикаторов и ARIA.
Мастер-аудит сайта: 6 измерений за один проход
Orchestrator-аудит по 6 направлениям: UX, accessibility, performance, SEO, brand consistency, security. Quick scan + deep dive + приоритизированный план + композитная оценка + roadmap.
Микроанимации интерфейса
Hover, focus, активные состояния, toast'ы, переходы — мелкая моторика интерфейса.