Prefers-reduced-motion

Системная настройка операционной среды, ограничивающая анимации, определяется через CSS Media Query:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Этот механизм сигнализирует, что пользователь предпочитает минимальную динамику интерфейса. Причины могут быть связаны с вестибулярной чувствительностью, когнитивной перегрузкой или снижением дискомфорта при длительной работе с интерфейсами.

В JavaScript доступ к этому состоянию осуществляется через matchMedia:

const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');

const isReducedMotion = reducedMotionQuery.matches;

Изменения предпочтения могут происходить динамически:

reducedMotionQuery.addEventListener('change', (event) => {
  console.log(event.matches ? 'reduced' : 'no-preference');
});

Поведение Motion One при системных ограничениях

Библиотека Motion One предоставляет встроенную поддержку системного параметра уменьшенной анимации. Основной механизм управляется параметром reduceMotion.

import { animate } from "@motionone/dom";

animate(
  ".box",
  { transform: ["translateX(0px)", "translateX(200px)"] },
  { duration: 1, reduceMotion: "user" }
);

Значение reduceMotion определяет стратегию поведения:

  • user — учитывается системная настройка prefers-reduced-motion
  • always — анимации полностью отключаются независимо от системы
  • never — анимации выполняются всегда

Поведение при user:

  • при prefers-reduced-motion: reduce анимации сокращаются до финального состояния
  • длительность фактически минимизируется
  • промежуточные ключевые кадры не проигрываются

Стратегии проектирования анимаций с учетом reduced motion

Сокращение движения вместо полного отключения

Полное удаление анимаций часто ухудшает восприятие интерфейса. Более устойчивый подход — замена движения на изменение прозрачности или состояния:

animate(
  ".modal",
  { opacity: [0, 1] },
  { duration: 0.2, reduceMotion: "user" }
);

При активированном reduced motion библиотека автоматически подавляет трансформации и сохраняет минимальную визуальную обратную связь.

Централизованное управление анимациями

При масштабных интерфейсах целесообразно использовать единый слой абстракции над Motion One.

import { animate } from "@motionone/dom";

const prefersReducedMotion =
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

function safeAnimate(element, keyframes, options = {}) {
  return animate(element, keyframes, {
    reduceMotion: "user",
    ...options,
    duration: prefersReducedMotion ? 0 : options.duration
  });
}

Такой слой позволяет унифицировать поведение всех анимаций без повторения логики в каждом вызове.

Влияние на timeline-анимации

Timeline в Motion One агрегирует последовательные анимации:

import { timeline } from "@motionone/dom";

timeline([
  [".a", { opacity: [0, 1] }, { duration: 0.3 }],
  [".b", { transform: ["translateY(20px)", "translateY(0px)"] }, { duration: 0.5 }]
], {
  reduceMotion: "user"
});

При активированном reduced motion последовательность сохраняется, но визуальное движение сводится к конечным состояниям, минимизируя когнитивную нагрузку.

Приоритет системного состояния и ручных настроек

Конфигурация reduceMotion может вступать в конфликт с локальными решениями. Приоритет определяется следующим образом:

  1. Явное значение always — отключает анимации полностью
  2. Системное значение prefers-reduced-motion
  3. Значение never — принудительное выполнение анимаций

Это позволяет строить предсказуемую модель поведения:

animate(".card", { opacity: [0, 1] }, { reduceMotion: "always" });

Учет reduced motion в сложных сценах

При работе с составными интерфейсами важно учитывать не только отдельные анимации, но и их взаимодействие:

  • параллельные движения элементов
  • каскадные появления
  • параллакс-эффекты
  • 3D трансформации

Параллакс при reduced motion требует полной замены на статическое позиционирование:

animate(
  ".background",
  { transform: ["translateY(0px)", "translateY(-100px)"] },
  { duration: 2, reduceMotion: "user" }
);

В режиме reduced motion фактическое смещение подавляется, предотвращая эффект движения фона.

Обработка событий и пользовательских переходов

Анимации, завязанные на пользовательские действия, также должны учитывать системные ограничения:

button.addEventListener("click", () => {
  animate(".panel", { opacity: [0, 1], scale: [0.95, 1] }, {
    duration: 0.25,
    reduceMotion: "user"
  });
});

При активированном reduced motion визуальный переход упрощается до мгновенного состояния без промежуточных фаз.

Интеграция с архитектурой UI

В крупных приложениях предпочтительно выделять слой управления motion-логикой:

export const motionConfig = {
  reduceMotion: "user",
  baseDuration: 0.4
};

Использование конфигурации:

import { animate } from "@motionone/dom";
import { motionConfig } from "./motionConfig";

function transition(element, props) {
  return animate(element, props, {
    reduceMotion: motionConfig.reduceMotion,
    duration: motionConfig.baseDuration
  });
}

Такой подход позволяет централизовать поведение и обеспечивает согласованность интерфейса.

Деградация анимаций до состояний

Наиболее стабильная модель при reduced motion — отказ от промежуточной динамики в пользу дискретных состояний интерфейса:

  • скрыто → видно
  • свернуто → раскрыто
  • неактивно → активно

Motion One автоматически поддерживает такой паттерн при использовании reduceMotion: "user", но архитектурно важно проектировать анимации так, чтобы финальное состояние было самостоятельным и корректным без переходов.