В интерфейсах, построенных на Popmotion, анимация часто является
частью вычислительной модели: значения изменяются во времени через
tween, spring, physics,
animate. При включённой системной настройке
prefers-reduced-motion вся эта модель требует адаптации —
не просто отключения эффектов, а перераспределения динамики
поведения.
Базовый механизм опирается на media query:
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
Этот флаг становится центральным источником принятия решений для всей анимационной подсистемы. Его использование обычно инкапсулируется в отдельный слой конфигурации, чтобы исключить повторные проверки на уровне каждого эффекта.
Popmotion работает с параметрами длительности, силы пружины, физическими коэффициентами. Для адаптации вводится нормализующий слой:
const motionConfig = {
reduced: {
duration: 0,
spring: {
stiffness: 2000,
damping: 100
},
distanceFactor: 0.2
},
full: {
duration: 300,
spring: {
stiffness: 500,
damping: 30
},
distanceFactor: 1
}
};
Выбор режима:
const config = prefersReducedMotion
? motionConfig.reduced
: motionConfig.full;
tween в Popmotion основан на временной интерполяции. В
режиме сниженного движения временная составляющая либо устраняется, либо
заменяется мгновенным переходом.
import { tween } from 'popmotion';
function safeTween(props) {
if (prefersReducedMotion) {
return tween({
from: props.from,
to: props.to,
duration: 0
});
}
return tween({
...props,
duration: config.duration
});
}
Такой подход сохраняет API неизменным, изменяя только поведение времени.
Физическая модель spring наиболее чувствительна к
восприятию движения. В режиме reduced motion корректируется не только
длительность, но и сама физика системы.
import { spring } from 'popmotion';
function safeSpring(props) {
if (prefersReducedMotion) {
return spring({
from: props.from,
to: props.to,
stiffness: 3000,
damping: 100
});
}
return spring({
...props,
stiffness: config.spring.stiffness,
damping: config.spring.damping
});
}
Высокая жёсткость и демпфирование фактически гасят колебания, превращая spring-переход в почти линейное приближение без инерционного хвоста.
Даже при сохранении анимации часто требуется уменьшение расстояния
перемещения. Это особенно важно для x, y,
scale, rotate.
Вводится универсальный коэффициент:
function reduceMotionValue(value) {
return prefersReducedMotion
? value * config.distanceFactor
: value;
}
Использование в анимации:
safeTween({
from: { x: 0 },
to: { x: reduceMotionValue(300) },
duration: config.duration
});
Такое масштабирование позволяет сохранить структуру интерфейса без резких перемещений.
Popmotion допускает управление значениями напрямую через
styler или value. В reduced-режиме часто
применяется моментальная установка состояния.
import { styler } from 'popmotion';
const box = styler(document.querySelector('.box'));
function setPosition(x) {
if (prefersReducedMotion) {
box.set({ x });
return;
}
safeSpring({ from: box.get('x'), to: x })
.start(box.set);
}
Здесь анимационный поток полностью исключается, но API вызова остаётся единым.
При увеличении количества анимаций становится необходим централизованный слой создания эффектов.
function animateX(from, to) {
return prefersReducedMotion
? tween({ from: { x: to }, to: { x: to }, duration: 0 })
: spring({
from: { x: from },
to: { x: to },
stiffness: config.spring.stiffness,
damping: config.spring.damping
});
}
Такая фабрика устраняет необходимость проверок внутри бизнес-логики компонентов.
Popmotion позволяет строить последовательности через композицию
chain, parallel и ручное управление
.start().
В режиме reduced motion последовательность сворачивается в синхронное применение конечных значений.
function runSequence(steps, apply) {
if (prefersReducedMotion) {
const last = steps[steps.length - 1];
apply(last.to);
return;
}
steps.reduce((prev, step) => {
return prev.start(() => {
step.start(apply);
});
});
}
При использовании Popmotion с событиями (listen, pointer
tracking) поведение также должно адаптироваться. Часто движение курсора
используется для параллакса или динамических смещений.
import { listen } from 'popmotion';
if (!prefersReducedMotion) {
listen(document, 'mousemove')
.start(({ clientX, clientY }) => {
box.set({
x: clientX * 0.05,
y: clientY * 0.05
});
});
}
В reduced режиме поток событий может полностью игнорироваться или заменяться статическим состоянием.
Для уменьшения дублирования логики вводится функция нормализации анимационных параметров:
function normalizeAnimation(params) {
if (prefersReducedMotion) {
return {
...params,
duration: 0,
stiffness: 3000,
damping: 100,
velocity: 0
};
}
return params;
}
Применение:
spring(normalizeAnimation({
from: 0,
to: 1,
stiffness: 500,
damping: 30
}));
В архитектуре Popmotion-ориентированных интерфейсов состояние
prefers-reduced-motion рассматривается как внешний источник
истины, перекрывающий локальные UI-флаги.
Даже если компонент запрашивает анимацию, финальное решение принимается на уровне адаптера:
function shouldAnimate(localFlag) {
return !prefersReducedMotion && localFlag;
}
В сложных интерфейсах движение делится на категории:
Для reduced motion сохраняется только критический уровень, остальные переводятся в мгновенные изменения или минимальные переходы без траектории.
function applyMotion(type, animation) {
if (prefersReducedMotion && type === 'decorative') {
animation.stop?.();
return;
}
animation.start();
}
В Popmotion временная ось часто используется как ключевой параметр
(elapsed, time, duration). В
reduced режиме она либо обнуляется, либо фиксируется:
const adjustedDuration = prefersReducedMotion ? 0 : 400;
Для physics-анимаций:
const adjustedPhysics = prefersReducedMotion
? { velocity: 0, friction: 1 }
: { velocity: 10, friction: 0.8 };
Архитектурно система строится как слой трансформации над примитивами Popmotion. Вместо модификации каждой анимации по отдельности вводится единый контур:
Так Popmotion сохраняет декларативную модель управления анимацией, но перестаёт зависеть от обязательного присутствия движения как визуального эффекта.