Использование Motion One в React строится вокруг идеи декларативного
управления анимациями с сохранением контроля над жизненным циклом
компонентов. Библиотека предоставляет низкоуровневые примитивы
animate, motion, timeline,
которые можно адаптировать под React-архитектуру без необходимости в
дополнительном слое абстракций.
React управляет DOM через виртуальное дерево, тогда как Motion One
напрямую работает с реальными DOM-узлами. Это создаёт необходимость
связывания элемента через ref.
Основная схема:
ref на DOM-элементimport { useEffect, useRef } from "react";
import { animate } from "motion";
export function FadeInBox() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const controls = animate(
ref.current,
{ opacity: [0, 1], transform: ["translateY(20px)", "translateY(0px)"] },
{ duration: 0.6, easing: "ease-out" }
);
return () => controls.stop();
}, []);
return <div ref={ref}>Контент</div>;
}
Ключевой момент заключается в том, что animate
возвращает контроллер, позволяющий остановить анимацию при
размонтировании компонента.
В React анимации часто зависят от состояния. Motion One не требует
отдельной системы transition — достаточно отслеживать изменения
state.
import { useEffect, useRef, useState } from "react";
import { animate } from "motion";
export function TogglePanel() {
const ref = useRef(null);
const [open, setOpen] = useState(false);
useEffect(() => {
if (!ref.current) return;
animate(
ref.current,
{
height: open ? 200 : 0,
opacity: open ? 1 : 0
},
{ duration: 0.4 }
);
}, [open]);
return (
<div>
<button onCl ick={() => setOpen(v => !v)}>toggle</button>
<div ref={ref} style={{ overflow: "hidden" }}>
Контент панели
</div>
</div>
);
}
Особенность подхода заключается в том, что Motion One не требует предварительного описания состояний анимации — достаточно описать переход между текущим и целевым значением.
motion для декларативных компонентовВ React можно использовать обёртку motion, позволяющую
работать с анимацией через props-подобный интерфейс.
import { motion } from "motion/react";
export function Card() {
return (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
>
Карточка
</motion.div>
);
}
Такой подход снижает необходимость ручного управления
ref, но сохраняет совместимость с Motion One API.
При работе с массивами данных ключевой задачей становится синхронизация анимаций с жизненным циклом элементов.
import { useEffect, useRef } from "react";
import { animate } from "motion";
export function List({ items }) {
const refs = useRef([]);
useEffect(() => {
refs.current.forEach((el, index) => {
if (!el) return;
animate(
el,
{ opacity: [0, 1], transform: ["translateX(10px)", "translateX(0px)"] },
{ delay: index * 0.05, duration: 0.3 }
);
});
}, [items]);
return (
<div>
{items.map((item, i) => (
<div
key={item.id}
ref={(el) => (refs.current[i] = el)}
>
{item.title}
</div>
))}
</div>
);
}
Важная особенность заключается в том, что управление DOM-коллекцией
происходит вручную через массив refs, что даёт полный
контроль над порядком анимации.
Motion One позволяет управлять жизненным циклом анимации через объект управления.
import { useEffect, useRef } from "react";
import { animate } from "motion";
export function ProgressBar({ value }) {
const ref = useRef(null);
const controls = useRef(null);
useEffect(() => {
if (!ref.current) return;
if (controls.current) {
controls.current.stop();
}
controls.current = animate(
ref.current,
{ width: `${value}%` },
{ duration: 0.3 }
);
}, [value]);
return <div ref={ref} style={{ height: 8, background: "black" }} />;
}
Такой подход предотвращает наложение анимаций при быстром изменении состояния.
timeline в ReactДля сложных последовательностей применяется timeline,
позволяющий строить цепочки анимаций.
import { useEffect, useRef } from "react";
import { timeline } from "motion";
export function Sequence() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const sequence = timeline([
[ref.current, { opacity: 1 }, { duration: 0.3 }],
[ref.current, { transform: "scale(1.1)" }, { duration: 0.2 }],
[ref.current, { transform: "scale(1)" }, { duration: 0.2 }]
]);
return () => sequence.stop();
}, []);
return <div ref={ref}>Блок</div>;
}
Timeline особенно полезен для UI-компонентов, где требуется строгое упорядочивание фаз анимации.
При условном рендеринге React не предоставляет встроенной анимации размонтирования, поэтому используется ручное управление.
import { useEffect, useRef, useState } from "react";
import { animate } from "motion";
export function Modal() {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (!ref.current) return;
if (visible) {
animate(ref.current, { opacity: [0, 1], scale: [0.9, 1] });
} else {
animate(ref.current, { opacity: [1, 0], scale: [1, 0.9] });
}
}, [visible]);
return (
<div>
<button onCl ick={() => setVisible(v => !v)}>open</button>
{visible && (
<div ref={ref} style={{ position: "fixed", inset: 0 }}>
Модальное окно
</div>
)}
</div>
);
}
Проблема здесь заключается в том, что размонтирование может происходить раньше завершения анимации, поэтому в реальных системах часто применяется задержка удаления DOM.
В режиме строгой проверки React вызывает эффекты дважды, что может приводить к повторному запуску анимаций. Для предотвращения этого используется хранение состояния и контроль запусков.
import { useEffect, useRef } from "react";
import { animate } from "motion";
export function SafeAnimation() {
const ref = useRef(null);
const started = useRef(false);
useEffect(() => {
if (!ref.current || started.current) return;
started.current = true;
const controls = animate(ref.current, {
opacity: [0, 1]
});
return () => controls.stop();
}, []);
return <div ref={ref}>Контент</div>;
}
Архитектурно удобный подход заключается в выделении анимации в кастомные хуки.
import { useEffect } from "react";
import { animate } from "motion";
export function useFadeIn(ref) {
useEffect(() => {
if (!ref.current) return;
const controls = animate(ref.current, {
opacity: [0, 1]
});
return () => controls.stop();
}, [ref]);
}
Использование:
export function Block() {
const ref = useRef(null);
useFadeIn(ref);
return <div ref={ref}>Элемент</div>;
}
Такой подход разделяет визуальную логику и структуру компонента, упрощая масштабирование интерфейса.
Motion One работает вне React render cycle, поэтому:
Это снижает нагрузку на reconciliation, но требует аккуратного управления побочными эффектами.
Особенно важно учитывать:
При сложных интерфейсах часто требуется координация нескольких DOM-узлов.
import { useEffect, useRef } from "react";
import { animate } from "motion";
export function Dashboard() {
const header = useRef(null);
const content = useRef(null);
useEffect(() => {
animate(header.current, { opacity: [0, 1], y: [-10, 0] });
animate(content.current, { opacity: [0, 1], y: [10, 0] });
}, []);
return (
<div>
<div ref={header}>Header</div>
<div ref={content}>Content</div>
</div>
);
}
Такая модель позволяет строить сложные композиции без внешнего state manager для анимаций.