Підтримка зменшення руху: prefers-reduced-motion виконано правильно

Було корисно?

Симптом у продакшені: запити в службу підтримки на кшталт «ваш сайт викликає нудоту», «скролінг відчувається як землетрус» або «сторінка входу мене ламає». Це не «переваги дизайну». Це баги надійності для людей.

Якщо ви випускаєте анімації без реальної стратегії зменшеного руху, ви ризикуєте доступністю, конверсією і інцидент-реакцією. Хороша новина: prefers-reduced-motion — одна з рідкісних можливостей веб-платформи, яка водночас гуманна і недорога в оперативному сенсі — за умови дисциплінованої реалізації.

Що насправді означає prefers-reduced-motion (і чого не означає)

prefers-reduced-motion — це користувацька підказка, доступна веб-платформі через CSS media queries і JavaScript API. Вона сигналізує, що користувач бажає менше руху. Менше руху може означати менше переходів, відсутність паралаксу, відсутність автоматичних анімацій, відсутність «скрол-джекінгу» і відмову від «корисного» плавного скролінгу, який тягне сторінку під курсором, ніби її буксирують.

Це не:

  • Прохання зробити ваш інтерфейс потворним.
  • Порада знизити частоту кадрів, зберігши ту саму анімацію (що може відчуватися ще гірше).
  • Місце, куди викидають «ми не хочемо дебажити це» запасні варіанти. Зменшений рух — не «зниження якості».

Уявляйте рух як залежність у продакшені. Він може відмовити. Він може перевантажити клієнтський пристрій. І він може нашкодити користувачам. Підтримка зменшеного руху — ваш запобіжник.

Оперативна реальність: баги руху — це баги розподілених систем

Анімації не працюють у вакуумі. Вони взаємодіють із:

  • плануванням CPU/GPU (особливо на інтегрованій графіці та в режимі енергозбереження)
  • вхідними пристроями (трекпади, коліщатка миші, сенсорні екрани)
  • продуктивністю layout і paint (яку ви цілком можете самі від DoS’нути)
  • життєвим циклом фреймворку (React/Angular/Vue повторні рендери vs. стан анімації)
  • сторонніми віджетами (реклама, чат, аналітика, A/B тестування)

Коли зменшення руху увімкнено, ваша система фактично працює в іншому режимі виконання. Ставтеся до цього відповідно: тестуйте, моніторте і не допускайте дрейфу.

Одна цитата, до якої я часто повертаюся, бо вона тут прямо підходить: «Надія — не стратегія.» — Gene Kranz

(Ваша стратегія анімацій не повинна бути «сподівання, що користувачі не помітять». Вони помічають.)

Історичний контекст і цікаві факти

  • Системні налаштування «зменшити рух» з’явилися до веб-фічі. Платформи додавали зменшення руху переважно для доступності та комфорту вестибулярного апарату; веб пізніше отримав стандартизований гачок.
  • prefers-reduced-motion — це фіча Media Queries Level 5. Вона входить у сучасну хвилю сигналів «користувацькі переваги» поруч з темною/світлою схемою кольорів.
  • Сигнал зазвичай бінарний, але реалізації різняться. Зазвичай ви бачите reduce vs no-preference; деякі середовища можуть поводитися інакше в embedded-контекстах.
  • «Плавний скролінг» швидко став дефолтом. Дизайнерам він сподобався; деякі користувачі почувалися фізично погано. Вирішення на рівні специфікації не було «заборонити», а «поважати перевагу».
  • Порушення вестибулярного апарату — не рідкісна крайність. Чутливість до руху може виникати через мігрень, захворювання внутрішнього вуха, струс мозку, ліки або просто вік. Ваша база користувачів — не демо 25-річних ентузіастів моушн-графіки.
  • Паралакс часто винуватець. Він пов’язує скролінг з шаровим рухом; для деяких користувачів це відчуття, ніби світ ковзає під ними.
  • Автовідтворювані анімації часто гірші за переходи. Вони можуть бути безжальними, особливо якщо зациклені й потрапляють у периферійне поле зору.
  • Продуктивність і доступність тут переплетені. Глюки анімації (втрачені кадри) можуть викликати дискомфорт більше, ніж плавний рух, тому «просто нехай сіпається» — не рішення.
  • Зменшення руху може покращити бізнес-метрики. Не тому що рух «поганий», а тому що усунення відволікань і нудоти зменшує відмови й агресивні кліки.

Жарт #1: Анімації як стажери — хороші під наглядом, катастрофічні, якщо залишити «вільно самовиражатися» у продакшені.

Критерій завершення: зменшення руху, яке не відкотиться

Більшість команд «підтримують зменшення руху», розкидавши пару CSS-правил у кутку і вважаючи справу зробленою. Це як додати правило фаєрвола і оголосити компанію безпечною.

Ось практичний критерій завершення, що витримає редизайни та міграції фреймворків:

1) У вас є одиний джерело істини для переваги

CSS читає її через медіа-запити, JS — через matchMedia. Але ваш додаток не повинен мати п’ять різних утиліт «isReducedMotion», які суперечать одна одній.

2) Ви класифікували рух, а не просто вимкнули його

Не весь рух однаковий. Класифікуйте:

  • Необхідний: передає зміну стану (наприклад, контур фокуса, ледь помітні зміни opacity) — можна скоротити, а не видаляти.
  • Корисний: допомагає сприйняттю (наприклад, короткий перехід розгорнути/згорнути). У режимі зменшеного руху: скоротити, прибрати overshoot і bounce.
  • Декоративний: фонові цикли, конфетті, паралакс, «дихаючі» картки. У режимі зменшеного руху — відключити.

3) Зменшення руху працює без JS

Починайте з CSS, потім доопрацьовуйте з JS. Якщо JS не спрацює, зменшення руху все одно має виконуватися для більшості ефектів.

4) Налаштування руху тестуються як першокласний варіант

Не «хтось перевірив на MacBook один раз». Вам потрібно:

  • покриття візуальних регресій для режиму reduce
  • юнит-тести для утиліт руху
  • явний крок QA у підписанні релізу

5) Сторонні джерела анімації під контролем

Lottie, маркетингові вставки, чат-вігети, накладки аналітики — звичні підозрювані. Потрібна політика: або вони поважають зменшення руху, або їх не запускають.

6) Ви можете пояснити, що відбувається при перемиканні

Користувач може змінити системні налаштування, поки ваш додаток відкритий. Ваш код має реагувати. Якщо анімації перевіряють налаштування лише при старті, ви зробили «фічу, яка працює в демо».

Швидкий план діагностики

Ви на чергуванні (або робите вигляд, що ні). Прийшов тікет: «Зменшений рух увімкнений, але сайт все одно анімується». Або гірше: «Зменшений рух увімкнений і сайт тепер зламався». Ось як швидко знайти вузьке місце.

По-перше: підтвердіть, що в середовищі дійсно встановлено «reduce»

  • Чи увімкнено системне налаштування?
  • Чи браузер його експонує (не в якомусь дивному embedded webview режимі)?
  • Чи ваш код читає його правильно?

По-друге: ідентифікуйте джерела руху

  • CSS переходи/анімації
  • JS-анімації (requestAnimationFrame цикли)
  • поведінка скролінгу (CSS плавний скрол або JS бібліотеки)
  • canvas/webgl цикли
  • вбудовані iframe або сторонні скрипти

По-третє: перевірте, чи «reduce» реалізовано як «відключити» або «скоротити»

Якщо ви виставили тривалості в 0ms глобально, ви можете створити нові баги: стрибки фокуса, треш у лейауті, проблеми з таймінгом подій, animationend слухачі ніколи не спрацьовують тощо.

По-четверте: перевірте оновлення під час виконання

Перемикайте системне налаштування, поки додаток відкритий. Якщо нічого не змінюється, швидше за все у вас кешоване значення при завантаженні модуля.

По-п’яте: перевірте регресії, внесені оптимізаціями

Звичні винуватці: «покращення продуктивності», які перемістили логіку анімацій у спільну утиліту, яка більше не читає перевагу, або CSS-рефактор, який замінив безпечні властивості на ті, що тригерять лейаут.

Патерни реалізації: CSS, JS і компонентні системи

CSS: базис, який завжди має бути

Почніть з політики, що застосовується по всьому додатку. Типовий базис:

  • відключити довготривалі декоративні анімації
  • зменшити або прибрати переходи (особливо трансформаційні «вилітання»)
  • відключити плавний скролінг

Механізм CSS простий:

  • @media (prefers-reduced-motion: reduce) для режиму reduce
  • @media (prefers-reduced-motion: no-preference) для за замовчуванням

Але «прямолінійність» — місце, де народжуються баги. Треба знати, що вимкнути, і де глобальні правила дають зворотний ефект:

Пастка глобального скидання

Ви бачили цей фрагмент:

  • set animation-duration: 0.001ms !important
  • set transition-duration: 0.001ms !important

Воно популярне, бо просте. Але небезпечне, тому що:

  • Ламає компоненти, які покладаються на часування переходів для очищення стану.
  • Може спричинити різкі стрибки, які відчуваються гірше за коротке згасання.
  • Приховує джерела руху в девелопменті, бо «все якось працює».

Робіть таргетований підхід: визначайте motion-токени (тривалості/easing) і підміняйте їх залежно від переваги. Вимикайте глобально лише дійсно декоративні петлі.

JS: прочитайте перевагу раз, потім слухайте зміни

Використовуйте window.matchMedia('(prefers-reduced-motion: reduce)'). Але не робіть це в десяти файлах. Обгорніть у модуль.

Важлива деталь: браузери змінювали API з часом; деякі середовища підтримують addEventListener('change', ...), старі — addListener. Ваш обгортковий модуль має підтримувати обидва варіанти.

Компонентні фреймворки: уникайте «руху як побічного ефекту»

У React та подібних рух часто стає побічним ефектом: хук запускає перехід на mount, бібліотека анімує зміни лейауту, мікро-інтеракція тригериться при hover.

Дві настанови, що вберігають від проблем:

  1. Робіть параметри руху явними пропсами. Тривалість, easing і включення руху не повинні бути захардкожені глибоко в компоненті.
  2. Ніколи не виводьте reduced motion з «продуктивності пристрою». Користувачі просять зменшення руху через комфорт, а не тому, що GPU втомився.

Скрол і навігація: ставтеся до плавного скролу як до інструменту

CSS scroll-behavior: smooth привабливий однією стрічкою. Він також один із найпростіших шляхів порушити зменшення руху.

Політика:

  • За замовчуванням: дозволяти плавний скрол лише для явної дії користувача (наприклад, клік «Перейти до розділу»), а не для довільних подій скролу.
  • У режимі reduce: відключити плавний скрол.

Canvas/WebGL: «зменшення руху» може означати «пауза»

Якщо ви запускаєте постійний рендер-цикл, трактуйте режим reduce як підказку зупинити або пригальмувати. Замініть фоновий рух статичним кадром або рендеріть за потреби. Бонус: економія батареї, вентилятор на ноуті перестане розкручуватися, і ніхто не писатиме тікети про запаморочення.

Жарт #2: Якщо ваш індикатор завантаження потребує підтримки зменшеного руху, вітаю — ви винайшли новий вид стрес-тесту для людей.

Практичні завдання з командами (і рішеннями)

Ось практичні завдання, які можна виконати сьогодні. Кожне містить: команду, приклад виводу, що це означає, і яке рішення прийняти.

Завдання 1: Знайти покриття prefers-reduced-motion у репозиторії

cr0x@server:~$ rg -n "prefers-reduced-motion" .
./src/styles/motion.css:12:@media (prefers-reduced-motion: reduce) {
./src/components/Carousel/carousel.css:88:@media (prefers-reduced-motion: reduce) {

Значення: У вас є два явні блоки для reduced-motion. Це може бути добре — або означати, що більшість компонентів його ігнорують.

Рішення: Якщо менше, ніж основні джерела руху, з’являються тут, заплануйте аудит руху (див. чекліст). Зменшений рух має бути системним, а не випадковим.

Завдання 2: Інвентаризація CSS анімацій і переходів

cr0x@server:~$ rg -n "(animation:|animation-|transition:|transition-)" src
src/styles/base.css:41:transition: all 300ms ease;
src/styles/toast.css:9:animation: slideIn 450ms cubic-bezier(.2,.8,.2,1);
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;

Значення: Маєте принаймні одну безкінечну анімацію (float) і одну потенційно небезпечну директиву transition: all.

Рішення: Безкінечні декоративні анімації повинні бути відключені в режимі reduce. Замініть transition: all на явні властивості (opacity/transform), потім керуйте тривалостями через motion-токени.

Завдання 3: Виявити «scroll-behavior: smooth»

cr0x@server:~$ rg -n "scroll-behavior:\s*smooth" src
src/styles/base.css:7:html { scroll-behavior: smooth; }

Значення: Плавний скрол глобальний. Класичне порушення для зменшеного руху.

Рішення: Зробіть умовним: за замовчуванням вимкнений або обмежений по області. У режимі reduce примусово виставляйте scroll-behavior: auto.

Завдання 4: Знайти requestAnimationFrame цикли, що можуть йти нескінченно

cr0x@server:~$ rg -n "requestAnimationFrame\(" src
src/visuals/background.ts:55:rafId = requestAnimationFrame(tick);
src/components/Chart/liveChart.ts:112:window.requestAnimationFrame(draw);

Значення: Є JS-рендер-цикли.

Рішення: Забезпечте, щоб ці цикли поважали reduced motion: поставте на паузу, відрендерте один кадр або переключіться на подієвий рендеринг. Також перевірте очищення на unmount, щоб уникнути витоків.

Завдання 5: Знайти Framer Motion / дефолти бібліотек анімацій

cr0x@server:~$ rg -n "(framer-motion|useReducedMotion|motion\.)" src
src/app/App.tsx:14:import { MotionConfig } from "framer-motion";
src/components/Modal/Modal.tsx:6:import { motion, useReducedMotion } from "framer-motion";

Значення: Ви використовуєте бібліотеку, яка має власну думку про рух. Добре — якщо налаштовано централізовано.

Рішення: Переконайтеся, що існує глобальна конфігурація (наприклад, MotionConfig) і що локальні переозначення компонентів не заново вмикають рух у режимі reduce.

Завдання 6: Виявити Lottie (часто ігнорує reduced motion за замовчуванням)

cr0x@server:~$ rg -n "(lottie|bodymovin)" src
src/components/EmptyState/EmptyState.tsx:3:import Lottie from "lottie-react";
src/components/PromoBanner/PromoBanner.tsx:8:import lottieData from "./promo.json";

Значення: Lottie-анімації в UI продукту, ймовірно зациклені.

Рішення: У режимі reduce: не автозапускати; показувати статичний кадр або альтернативне зображення. Ставте це як обов’язкове, не «приємне доповнення».

Завдання 7: Перевірити, чи дизайн-токени включають motion-токени

cr0x@server:~$ rg -n "(--duration|--easing|motion)" src/styles
src/styles/tokens.css:12:--duration-fast: 120ms;
src/styles/tokens.css:13:--duration-medium: 220ms;
src/styles/tokens.css:14:--duration-slow: 360ms;

Значення: У вас є CSS-змінні, пов’язані з рухом. Гарна база.

Рішення: Додайте overrides для режиму reduced motion замість того, щоб усе зануляти з !important. Наприклад: скоротіть тривалості і видаліть bounce-easing.

Завдання 8: Підтвердити, що Playwright тести можуть емулювати reduced motion

cr0x@server:~$ rg -n "reducedMotion" tests
tests/e2e/login.spec.ts:9:  await page.emulateMedia({ reducedMotion: "reduce" });

Значення: Принаймні один тест використовує емулювання reduced motion.

Рішення: Розширте покриття для сторінок з великою кількістю руху. Додайте тест, який перевіряє відсутність нескінченних анімацій і відсутність плавного скролінгу в режимі reduce (наскільки це можна перевірити через DOM/CSS).

Завдання 9: Швидка перевірка бандлу на поліфіли плавного скролу

cr0x@server:~$ rg -n "(smoothscroll|scrollTo\(\{|behavior:\s*\"smooth\")" src
src/lib/navigation.ts:28:window.scrollTo({ top: 0, behavior: "smooth" });

Значення: JS примусово застосовує плавний скрол незалежно від налаштування користувача.

Рішення: Обгорніть цей виклик: якщо reduced motion увімкнено — використовуйте behavior: "auto" або опустіть опцію behavior.

Завдання 10: Виявити залежності від подій transitionend/animationend

cr0x@server:~$ rg -n "(transitionend|animationend)" src
src/components/Drawer/Drawer.tsx:88:el.addEventListener("transitionend", onDone);

Значення: Логіка компонентів залежить від завершення переходів.

Рішення: В режимі reduced motion тривалості можуть стати настільки короткими, що події поводяться інакше, або переходи взагалі відключені. Додайте явний шлях: якщо reduced motion увімкнено, викликайте onDone() негайно (або після мікротаску), не чекаючи подій, які можуть ніколи не спрацювати.

Завдання 11: Підтвердити сторонні скрипти, що можуть анімувати накладки

cr0x@server:~$ ls -1 public/vendor
chat-widget.js
marketing-overlay.js

Значення: Існують vendor-скрипти, які можуть інжектити анімації поза контролем вашого CSS.

Рішення: Аудитуйте продавців. Якщо вони не підтримують reduced motion, обгортайте: відключайте автоплей, приховуйте оверлеї або підвантажуйте альтернативну конфігурацію при увімкненні режиму reduce.

Завдання 12: Локальна перевірка в Chromium через DevTools (емуляція — не реальність, але допомагає)

cr0x@server:~$ chromium --user-data-dir=/tmp/chrome-prm --enable-features=WebContentsForceDark
[12874:12874:1229/101512.116955:INFO:chrome_main_delegate.cc(594)] Started

Значення: Ви запустили чистий профіль Chromium, щоб уникнути шуму розширень. (Показаний флаг не про рух; суть — ізолювати змінні.)

Рішення: У панелі DevTools → Rendering емулюйте «prefers-reduced-motion» і візуально інспектуйте ключові потоки. Потім повторіть з фактичним увімкненим системним налаштуванням, щоб виявити невідповідності.

Завдання 13: Перевірка, що ви не глобально змушуєте «no-preference» у CSS

cr0x@server:~$ rg -n "prefers-reduced-motion:\s*no-preference" src
src/styles/motion.css:33:@media (prefers-reduced-motion: no-preference) {

Значення: У вас є явне стилювання для режиму за замовчуванням. Це нормально.

Рішення: Переконайтеся, що блок «no-preference» випадково не перезаписує reduce через специфічність. Краще мати override для «reduce», який перемагає (і протестуйте це).

Завдання 14: Перевірити CSS анімації, що можуть працювати навіть коли елемент прихований

cr0x@server:~$ rg -n "infinite" src/styles src/components
src/components/Hero/hero.css:22:animation: float 4s ease-in-out infinite;
src/components/Background/bg.css:11:animation: shimmer 1.6s linear infinite;

Значення: Існують безкінечні петлі. Шимери на skeleton-скрінах — частий винуватець.

Рішення: У режимі reduce перетворіть shimmer на статичний заповнювач. Для hero-флоатів — заморозьте. Ваш CPU (і ваші користувачі) будуть вдячні.

Три корпоративні міні-історії з реального життя

Міні-історія 1: Інцидент через хибне припущення

Середня B2B SaaS команда випустила редизайн з новою стильнною бічною панеллю. Висувний екран заїжджав, фон розмитий, накладка зникає з фейдом. Також був тумблер reduced motion — формально. Його реалізували глобальним CSS-сніпетом, що виставляв тривалості анімацій майже в нуль у режимі reduce.

На папері виглядало сумісно. На практиці drawer-компонент слухав transitionend, щоб перемістити фокус в панель і правильно виставити aria-hidden на фон. З тривалістю, ефективно нуль, подія не завжди спрацьовувала в різних браузерах. Інколи вона траплялася до того, як слухач приєднався. Іноді взагалі не спрацьовувала.

Користувачі з увімкненим reduced motion почали писати «клавіатурна навігація зламана» і «сторінка застрягла». Підтримка підняла інцидент як outage з доступності. Інженери спочатку вважали це дрібною крайністю, бо «відбувається лише при увімкненому reduced motion». Це речення дуже швидко перестало бути правдою.

Виправлення було нудне і правильне: станова машина drawer припинила покладатися на події переходів як на джерело істини. Переходи стали косметичними. Якщо reduce увімкнено, виконується детермінований шлях: встановити DOM-стан, виставити фокус, пропустити анімації і синхронно викликати очищення.

Хибне припущення було не в CSS. Воно полягало в думці, що reduced motion — це «лише коротші тривалості». Ні.

Міні-історія 2: Оптимізація, що дала зворотний ефект

Велика команда споживчого продукту мала проблеми з продуктивністю на слабких пристроях. Їхнє рішення: замінити кілька CSS-переходів на JS-оркестратор анімацій, щоб «пакувати оновлення» і «уникати layout thrash». Вони також консолідували безліч мікро-інтеракцій у єдину спільну таймлайн.

Бенчмарки показали покращення. Реальні звіти — ні. Оркестратор використовував requestAnimationFrame цикли, які працювали безперервно для «відповідності», навіть коли анімації були неактивні. Він також за замовчуванням вмикав плавний скрол при навігації сторінкою. Підтримка reduced motion була лише в CSS, тож жодна JS-логіка анімацій його не поважала.

Користувачі з увімкненим reduced motion все одно бачили паралакс і плавні скрол-ефекти. Деякі також помітили підвищений розряд батареї, бо постійний rAF-цикл тримав сторінку «гарячою». Команда спочатку звинувачувала ОС. Потім браузер. Потім «можливо, користувачі щось не так налаштували». Класика.

Вони виправили це, зробивши крок, що виглядав як відкат: повернули більшість взаємодій назад у CSS (де платформа може оптимізувати), і побудували єдиний JS-модуль «motion preference», який фильтрує всі точки входу анімацій. Також змінили rAF-цикл на подієвий: працює лише тоді, коли анімація активна.

Проблема не в тому, що JS-анімації завжди погані. Проблема в тому, що оптимізація ігнорувала операційну вимогу: зменшення руху має застосовуватись однаково до всіх джерел руху.

Міні-історія 3: Нудна, але правильна практика, що врятувала ситуацію

Веб-додаток, пов’язаний із фінансами, мав строгий процес релізу. Не гламурно. Команда підтримувала невеликий «контракт доступності» як чекліст для кожної UI-зміни. Один пункт: «Протестувати з увімкненим reduced motion на принаймні одній ОС і в автоматизованому E2E для потоків з великою кількістю руху».

Дизайнер запропонував новий онбординг з анімованими ілюстраціями і скрол-гідами. Інженери реалізували це з популярною бібліотекою анімацій і кількома Lottie-файлами. Під час передрелізної перевірки QA прогнала чекліст reduced motion і виявила, що гайд все одно автоскролить між кроками, навіть коли увімкнено reduced motion.

Виправлення було простим: у режимі reduce гайд припинив авто-прогрес і замінив скрол-переходи миттєвими переходами плюс чітке керування фокусом. Lottie-файли замінили статичними кадрами. Без драми, без інцидентів, без публічних вибачень.

Нічого тут не було хитрого. Практика працювала, бо була повторюваною, примусовою і прив’язаною до релізів як ремінь безпеки. Ви цього не помічаєте, поки воно не знадобилося.

Поширені помилки (симптоми → корінь → виправлення)

1) Симптом: «Увімкнено reduced motion, але паралакс все одно працює»

Корінь: Паралакс реалізовано в JS scroll-обробниках (або бібліотеці), які ніколи не перевіряють перевагу.

Виправлення: Обгортайте ініціалізацію паралаксу перевіркою reduced motion. У режимі reduce рендерьте статичний фон і видаляйте обробники скролу.

2) Симптом: «Reduced motion ламає модальні/дровери (фокус застрягає, накладка дивна)»

Корінь: Зміни стану залежать від transitionend/animationend подій.

Виправлення: Зробіть стан компонентів детермінованим; тримайте анімації факультативними. У режимі reduce викликайте обробники завершення безпосередньо і керуйте фокусом синхронно.

3) Симптом: «Все миттєво скаче і відчувається гірше»

Корінь: Глобальне «тривалість в 0ms» скидання. Різкі стрибки дезорієнтують.

Виправлення: Скорочуйте, а не усувайте для необхідних переходів; відключайте лише декоративний рух. Використовуйте motion-токени та налаштовуйте easing.

4) Симптом: «Шимери скелетонів блимають вічно навіть у режимі reduce»

Корінь: Безкінечна CSS-анімація не переозначена для режиму reduce.

Виправлення: Замініть shimmer на статичний заповнювач у режимі reduce. Розгляньте тонкий кольоровий блок без руху.

5) Симптом: «Плавний скрол все ще спрацьовує в режимі reduce»

Корінь: scroll-behavior: smooth встановлено глобально, або JS scrollTo({behavior:"smooth"}) викликається без умов.

Виправлення: У режимі reduce примусово виставляйте scroll-behavior: auto, а JS-скрол інкапсулюйте перевіркою переваги.

6) Симптом: «Reduced motion працює при завантаженні сторінки, але не після перемикання ОС»

Корінь: Перевага читається один раз і кешується; немає підписки на зміни.

Виправлення: Підпишіться на зміни медіа-запиту і оновлюйте централізований стор; перевідрендерте конфігурації руху відповідно.

7) Симптом: «Сторонній маркетинговий банер продовжує анімуватися»

Корінь: Віджет продавця ігнорує reduced motion і інжектить свій CSS/JS.

Виправлення: Завантажуйте конфіг для reduced motion від продавця або приховуйте віджет у цьому режимі. Зробіть це вимогою при закупівлі.

8) Симптом: «Використання CPU лишається високим, навіть коли нічого не відбувається»

Корінь: Ідле rAF-цикл, цикл рендерингу canvas або анімований фон працюють постійно.

Виправлення: Зупиняйте рендеринг, коли це не потрібно; у режимі reduce за замовчуванням ставте на паузу/статичний стан. Гарантуйте очищення на unmount.

9) Симптом: «Reduced motion ламає аналітику або A/B тести»

Корінь: Код експерименту залежить від часу анімацій або вимірює події, пов’язані з переходами.

Виправлення: Використовуйте явні події стану замість подій анімацій. Інструментуйте бізнес-події незалежно від руху.

Чеклісти / покроковий план

Крок 1: Проведіть інвентар руху (півдня, високий ROI)

  1. Пошукайте CSS анімації/переходи і перелічіть компоненти, що їх використовують.
  2. Пошукайте використання rAF і обробників скролу.
  3. Перелічіть сторонні віджети, що малюють на екрані (реклами, чат, маркетингові накладки).
  4. Класифікуйте кожен елемент руху як необхідний/корисний/декоративний.

Крок 2: Встановіть motion-токени (щоб змінювати поведінку без репо-ханту)

  1. Створіть токени тривалості (fast/medium/slow) і easing-токени (standard/emphasized).
  2. Замініть хардкод тривалостей і transition: all на токени і явні властивості.
  3. У режимі reduce скоротіть тривалості і приберіть bounce/overshoot easing-и.

Крок 3: Реалізуйте reduced motion в CSS спочатку

  1. Вимкніть безкінечні декоративні анімації.
  2. Вимкніть плавний скролінг.
  3. Переконайтеся, що ключові взаємодії все ще передають стан (контури фокуса, зміни вибору).

Крок 4: Реалізуйте reduced motion в JS через єдиний модуль переваги

  1. Обгорніть matchMedia у утиліту, яка експонує поточне значення і механізм підписки.
  2. Гейтніть ініціалізацію паралаксу, автозапуск анімацій і рендер-циклів.
  3. Обробляйте перемикання під час виконання, оновлюючи стан додатку (або перезавантажуючи конфіг руху).

Крок 5: Зробіть компоненти стійкими до «відсутності анімацій»

  1. Приберіть залежність від transitionend/animationend для коректності.
  2. Переконайтеся, що керування фокусом і ARIA-стани виконуються детерміновано.
  3. Перевірте, що відсутність анімацій не змінює лейаут несподівано.

Крок 6: Додайте автоматизовані тести (інакше майбутнє «я» переживе цей баг)

  1. Додайте E2E з емулюванням reduced motion для ключових потоків (аутентифікація, оплата, онбординг).
  2. Додайте регресійні тести, що перевіряють відключення основних джерел руху (наприклад, відсутність безкінечних CSS-анімацій на критичних сторінках).
  3. Додайте lint-правило або CI-чек, що флагуватиме scroll-behavior: smooth і transition: all, якщо вони не схвалені.

Крок 7: Операціоналізуйте це

  1. Додайте перевірку reduced motion у підпис до релізу.
  2. Документуйте вашу «політику руху» в репозиторії (коротко, примусово, актуально).
  3. Переконайтеся, що сторонні продавці перевіряються на підтримку reduced motion.

FAQ

Q1: Чи reduced motion тільки для людей з інвалідністю?

Ні. Чутливість до руху може бути ситуативною або тимчасовою: мігрень, ліки, відновлення після струсу, запаморочення, втома, навіть поїздка в трясучому транспорті. Пишіть для людей, а не для ярликів.

Q2: Чи треба вимикати всі переходи при увімкненому reduced motion?

Вимикайте декоративний рух і безкінечні петлі. Для необхідних переходів віддавайте перевагу коротким, тонким змінам (часто opacity) а не драматичному руху. Мета — не «відсутність руху», а «відсутність відсутності зворотного зв’язку».

Q3: Чи «duration: 0ms» — валідна стратегія?

Це грубий інструмент. Він може зламати компоненти, що залежать від подій анімації чи таймінгу, і створити різкі стрибки. Використовуйте токени і явні логічні шляхи для коректності.

Q4: Як поводитися з плавним скролом?

Вимкніть плавний скрол у режимі reduce. Також уникайте глобального ввімкнення. Якщо потрібен для jump link, робіть його в JS і лише для дій ініційованих користувачем.

Q5: А як щодо мікро-інтеракцій на hover?

Ефекти hover можуть залишатися, якщо вони тонкі і не містять руху (колір/opacity). Уникайте перекладів або поворотів при hover у режимі reduce, особливо в щільних інтерфейсах де hover відбувається часто.

Q6: Чи потрібно реагувати на зміну переваг під час відкритого додатку?

Так. Користувач може перемкнути системні налаштування без перезапуску вкладки. Слухайте зміни медіа-запиту і оновлюйте конфігурацію руху.

Q7: Як працювати з бібліотеками анімацій?

Обирайте бібліотеки, що мають режим reduced motion або приймають параметри. Налаштовуйте централізовано. Потім забороніть компонентам перевизначати поведінку reduced motion, щоб знову вмикати рух.

Q8: Який найшвидший спосіб виявити регресії reduced motion?

Додайте smoke E2E тест, що завантажує сторінку з великою кількістю руху з емульованим reduced motion і перевіряє, що ключові елементи не анімуються (або що класи анімацій відсутні). Доповніть це ручною перевіркою на рівні ОС під час QA релізу.

Q9: Якщо reduced motion увімкнено, чи можна залишати спінери?

Так, але надавайте перевагу індикаторам без обертання: прогрес-бари, крапки, що зʼявляються без руху, або статичний текст «Loading…». Якщо залишите спінер, зупиніть його в режимі reduce або замініть на м’яке згасання.

Висновок: наступні кроки, які можна зробити цього тижня

Підтримка зменшеного руху — це не «полірувальний» елемент. Це режим виконання, що впливає на коректність, продуктивність і довіру користувачів. Реалізуйте його так само, як будь-яку операційну вимогу: централізовано, тестовано і з планом на випадок хаосу від сторонніх компонентів.

Практичні кроки:

  1. Запустіть пошуки по репо з наведених прикладів і складіть інвентар руху.
  2. Приберіть глобальний плавний скрол і безкінечні декоративні анімації в режимі reduce.
  3. Реалізуйте єдиний JS-модуль переваги і підключіть його до точок входу анімацій.
  4. Виправте компоненти, що покладаються на події анімацій для коректності.
  5. Додайте хоча б один E2E тест з емульованим reduced motion для вашого основного потоку користувача.
  6. Додайте пункт у чекліст релізу, щоб це не регресувало в наступному кварталі.

Якщо зробите лише одне: перестаньте ставитися до руху як до декору. У продакшені рух — це поведінка. Поведінка потребує контролю.

← Попередня
MySQL vs MariaDB: затримки оформлення в WooCommerce — одна настройка вирішує, інша лише маскує
Наступна →
Proxmox VM не запускається після зміни типу CPU: робочі кроки відновлення

Залишити коментар