Анимации, завязанные на фиксированные пиксельные значения, теряют предсказуемость при изменении размеров экрана. Один и тот же переход может выглядеть корректно на десктопе и ломаться на мобильных устройствах из-за иной плотности контента, другого viewport и изменённой геометрии элементов.
В адаптивной системе анимации ключевым становится не абсолютное значение, а относительное поведение: смещения, длительности и траектории должны вычисляться исходя из текущего состояния layout.
Motion One предоставляет API, который позволяет комбинировать декларативные параметры анимации с динамическим пересчётом значений на основе состояния DOM.
Первый уровень адаптации — переход от пикселей к viewport-единицам:
vw — ширина окнаvh — высота окна% — относительные размеры контейнераimport { animate } from "motion";
animate(".box", {
x: "50vw",
opacity: [0, 1]
}, {
duration: 0.8
});
Использование vw/vh позволяет избежать необходимости
вручную пересчитывать значения при каждом изменении экрана. Однако
viewport-единицы ограничены: они не учитывают локальную геометрию
контейнера и особенности flex/grid раскладки.
Для более точной адаптации используется измерение элементов через
getBoundingClientRect():
import { animate } from "motion";
const element = document.querySelector(".card");
const rect = element.getBoundingClientRect();
animate(element, {
x: window.innerWidth - rect.right - 20
});
Такой подход позволяет строить анимации, привязанные к реальному положению элемента, а не к абстрактной сетке.
Типичные сценарии:
Изменение размеров окна не единственный фактор: layout может меняться при:
Для отслеживания таких изменений используется
ResizeObserver:
import { animate } from "motion";
const box = document.querySelector(".box");
const updateAnimation = () => {
const rect = box.getBoundingClientRect();
animate(box, {
x: rect.width * 0.5,
y: rect.height * 0.2
}, {
duration: 0.5
});
};
const observer = new ResizeObserver(updateAnimation);
observer.observe(box);
Ключевой принцип: анимация не привязана к статическому состоянию, а пересчитывается при каждом изменении геометрии.
Motion One поддерживает передачу функций вместо фиксированных значений. Это позволяет вычислять параметры на лету:
import { animate } from "motion";
animate(".item", {
x: (latest, element) => {
return element.offsetWidth * 0.3;
}
});
Функциональный подход даёт доступ к:
Это особенно полезно при построении интерфейсов с динамическими сетками.
Адаптация часто требует изменения не только значений, но и самой логики анимации.
import { animate } from "motion";
const isMobile = window.matchMedia("(max-width: 768px)").matches;
animate(".panel", {
x: isMobile ? 0 : 200,
opacity: [0, 1]
}, {
duration: isMobile ? 0.3 : 0.8
});
Более гибкий вариант — подписка на изменения media query:
const mq = window.matchMedia("(max-width: 768px)");
mq.addEventListener("change", (e) => {
animate(".panel", {
x: e.matches ? 0 : 200
});
});
Такой подход позволяет перестраивать поведение анимации без перезагрузки страницы.
Timeline в Motion One чувствителен к длительности и задержкам, которые должны масштабироваться вместе с интерфейсом.
import { timeline } from "motion";
const isCompact = window.innerWidth < 600;
timeline([
[".header", { opacity: [0, 1] }, { duration: isCompact ? 0.2 : 0.6 }],
[".content", { y: [50, 0] }, { at: "-0.3" }]
]);
В компактных интерфейсах сокращается время переходов, чтобы уменьшить визуальную задержку восприятия.
При использовании scroll-based эффектов важно учитывать разную высоту экранов.
import { animate, scroll } from "motion";
scroll(
animate(".parallax", {
y: ["0%", "-30%"]
}),
{
offset: ["start end", "end start"]
}
);
На мобильных устройствах те же значения могут приводить к чрезмерному смещению. Поэтому оффсеты часто вычисляются динамически:
const isSmall = window.innerHeight < 700;
scroll(
animate(".parallax", {
y: ["0%", isSmall ? "-15%" : "-40%"]
})
);
Универсальный подход к адаптации — использование коэффициента масштабирования:
const scale = Math.min(window.innerWidth / 1200, 1);
animate(".modal", {
scale: 0.8 + scale * 0.2,
y: (1 - scale) * 40
});
Такой метод позволяет:
Изменения layout (например, загрузка изображений) могут ломать уже запущенные анимации. Решение — пересчёт параметров при каждом значимом shift:
const images = document.querySelectorAll("img");
images.forEach(img => {
img.addEventListener("load", () => {
const rect = img.getBoundingClientRect();
animate(img, {
y: rect.height * 0.1
});
});
});
В интерфейсах с динамическим контентом это предотвращает накопление ошибок позиционирования.
На практике используется не один, а несколько слоёв адаптации:
Motion One в таком подходе выступает как исполнитель анимаций, тогда как вся адаптивная логика выносится на уровень вычисления параметров.
Структурирование адаптации в отдельные функции повышает предсказуемость поведения:
const getResponsiveX = (el) => {
const width = el.offsetWidth;
const scale = window.innerWidth / 1000;
return width * 0.2 * scale;
};
animate(".card", {
x: (latest, el) => getResponsiveX(el)
});
Такой подход позволяет строить библиотеку адаптивных анимационных примитивов, независимых от конкретного layout.