В React-обёртке Motion One анимации строятся вокруг идеи разделения императивного управления анимацией и реактивного жизненного цикла компонентов. Вместо того чтобы описывать анимации декларативно через сложные конфигурации, хуки дают прямой доступ к движку анимации, позволяя управлять DOM-элементами, значениями и событиями прокрутки с высокой точностью.
Ключевая особенность подхода — минимальная прослойка между React и нативным движком анимации. Это обеспечивает предсказуемую производительность и отсутствие лишних перерендеров при изменении анимационных значений.
Базовый хук для работы с анимациями — useAnimate. Он
предоставляет функцию запуска анимации и ссылку на DOM-узел.
Основная идея заключается в том, что анимация вызывается как побочный эффект, а не как часть рендера.
import { useAnimate } from "@motionone/react";
import { useEffect } from "react";
function Box() {
const [scope, animate] = useAnimate();
useEffect(() => {
animate(
scope.current,
{ transform: "translateX(200px)", opacity: 0.5 },
{ duration: 0.8, easing: "ease-in-out" }
);
}, []);
return <div ref={scope} className="box" />;
}
scope — ref-объект, привязанный к DOM-элементуanimate — функция запуска анимацииЦепочки позволяют последовательно запускать несколько переходов:
useEffect(() => {
animate(scope.current, { x: 100 });
animate(scope.current, { opacity: 0 });
}, []);
В реальных сценариях такие вызовы часто объединяются через промисы или async-последовательность, чтобы контролировать порядок выполнения.
Одной из ключевых проблем интеграции анимаций в React является
синхронизация с монтажом и размонтированием компонентов.
useAnimate автоматически привязывается к жизненному циклу
через ref-ссылку.
При размонтировании компонента:
Это предотвращает утечки памяти и неконсистентное состояние интерфейса.
useScroll позволяет привязывать анимационные значения к
позиции скролла. Это основа для создания параллакса, прогресс-баров и
динамических интерфейсов.
import { useScroll } from "@motionone/react";
function ScrollProgress() {
const { scrollYProgress } = useScroll();
scrollYProgress.on("change", (v) => {
console.log(v);
});
return <div className="progress" />;
}
Значение scrollYProgress представляет собой
нормализованный диапазон:
0 — начало прокрутки1 — конец страницы или контейнераЭто позволяет напрямую использовать значение для трансформаций:
const { scrollYProgress } = useScroll();
scrollYProgress.on("change", (v) => {
document.body.style.setProperty("--progress", v);
});
Важный режим работы useScroll — отслеживание прокрутки
конкретного контейнера.
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref
});
Такой подход позволяет:
Motion One использует концепцию motion values — реактивных значений, которые обновляются без ререндеринга React-компонента.
Типичный сценарий:
import { useMotionValue } from "@motionone/react";
function Example() {
const x = useMotionValue(0);
x.on("change", (latest) => {
console.log(latest);
});
return <div />;
}
Это делает их аналогом observable-подхода в реактивном программировании.
Для создания инерционных эффектов используется
useSpring. Он преобразует резкие изменения значения в
плавное движение.
import { useSpring, useMotionValue } from "@motionone/react";
function SpringBox() {
const x = useMotionValue(0);
const springX = useSpring(x, { stiffness: 120, damping: 20 });
return <div />;
}
Такая модель позволяет имитировать физическое движение без ручного расчета интерполяции.
Один из наиболее распространённых паттернов — сглаживание прокрутки через spring:
const { scrollY } = useScroll();
const smoothScroll = useSpring(scrollY, {
stiffness: 100,
damping: 30
});
Далее smoothScroll может использоваться для:
Motion One позволяет комбинировать motion values в вычисляемые зависимости:
const x = useMotionValue(100);
const y = useMotionValue(50);
const transform = useTransform(
[x, y],
([latestX, latestY]) => `translate(${latestX}px, ${latestY}px)`
);
Такой подход создаёт реактивную цепочку, где изменения одного значения автоматически пересчитывают производное.
useTransform используется для преобразования входных
значений в новые диапазоны.
Пример масштабирования:
const scale = useTransform(scrollYProgress, [0, 1], [1, 1.5]);
Здесь происходит линейное отображение:
Это удобно для:
Основная особенность Motion One — отсутствие необходимости перерендеров при каждом кадре анимации. Это достигается через:
Практическое следствие:
Хуки Motion One могут использоваться не только для автоматических эффектов, но и для управления через события:
function Button() {
const [scope, animate] = useAnimate();
const handleClick = () => {
animate(scope.current, {
scale: [1, 1.2, 1]
}, {
duration: 0.4
});
};
return <button ref={scope} onCl ick={handleClick} />;
}
Такая модель позволяет создавать сложные интерактивные микроанимации без сторонних стейт-менеджеров.
В сложных интерфейсах часто используется комбинация:
useScrolluseSpringuseTransformuseAnimateПример связки:
const { scrollYProgress } = useScroll();
const scale = useSpring(
useTransform(scrollYProgress, [0, 1], [0.8, 1.2])
);
useEffect(() => {
scale.on("change", (v) => {
scope.current.style.transform = `scale(${v})`;
});
}, []);
Такая композиция позволяет строить реактивные сцены, где один источник данных управляет множеством визуальных параметров.
useAnimate поддерживает работу не только с одним
элементом, но и с группами внутри одного scope:
const [scope, animate] = useAnimate();
useEffect(() => {
animate(scope.current.querySelectorAll(".item"), {
opacity: 1,
y: 0
});
}, []);
Это упрощает:
Распространённый паттерн — каскадное появление элементов:
animate(
".item",
{ opacity: 1, y: 0 },
{ delay: stagger(0.1) }
);
Хотя stagger является частью API анимации, в React-хуках он часто
применяется внутри useEffect, чтобы синхронизировать
появление элементов после монтирования.
Внутренняя модель Motion One в React опирается на три уровня:
Такое разделение позволяет:
Использование хуков накладывает ряд архитектурных особенностей:
Эти ограничения вытекают из асинхронной природы анимационного цикла и модели подписок.