Motion для React
motion.div, initial/animate/exit, AnimatePresence, variants и stagger, layout, прокрутка, пружины.
Обновлено
Что такое Motion
Motion (бывший Framer Motion) — самая популярная библиотека анимаций в React. Идея: вы заменяете HTML-тег его версией motion. и описываете состояния, а не шаги — Motion сам вычисляет анимацию между ними.
npm i motion'use client'
import { motion } from 'motion/react'
export function Card() {
return <motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }} />
}Компоненты motion.* — Client Components, в файле есть 'use client'. Страницу можно оставить Server Component и анимировать только маленькие части.
Базовые props
| Prop | Что делает |
|---|---|
initial |
начальное состояние (при монтировании) |
animate |
целевое состояние — когда оно меняется, Motion анимирует к нему |
exit |
состояние при размонтировании (нужен AnimatePresence) |
transition |
duration, ease, delay или type: 'spring' со stiffness / damping / bounce |
whileHover, whileTap, whileFocus |
временные состояния при взаимодействии |
whileInView |
состояние, когда элемент появляется на экране (+ viewport={{ once: true }}) |
layout |
автоматически анимирует изменения размера / позиции в раскладке |
layoutId |
два разных элемента с одним id «превращаются» друг в друга |
Уход: AnimatePresence
React сразу удаляет элемент из DOM, когда он больше не рендерится, — на анимацию нет времени. AnimatePresence держит его, пока не закончится exit:
<AnimatePresence>
{open && (
<motion.div key="panel" initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} />
)}
</AnimatePresence>Variants и stagger
Variants = именованные состояния, описанные один раз и передающиеся детям. Идеально для списков:
import { motion, stagger } from 'motion/react'
const list = { hidden: {}, show: { transition: { delayChildren: stagger(0.06) } } }
const item = { hidden: { opacity: 0, y: 16 }, show: { opacity: 1, y: 0 } }
<motion.ul variants={list} initial="hidden" whileInView="show" viewport={{ once: true }}>
{products.map(p => <motion.li key={p.id} variants={item}>{p.name}</motion.li>)}
</motion.ul>Детям не нужны initial / animate — они наследуют имена состояний от родителя.
Layout-анимации — эффект «вау» одной строкой
<motion.div layout className={expanded ? 'w-full' : 'w-40'} /> // размер анимируется автоматически{tabs.map(tab => (
<button key={tab} onClick={() => setActive(tab)} className="relative px-3 py-1">
{active === tab && <motion.span layoutId="tab-indicator" className="absolute inset-0 rounded-md bg-hover" />}
<span className="relative">{tab}</span>
</button>
))}Индикатор «скользит» от одной вкладки к другой — без расчёта позиций.
Прокрутка
const { scrollYProgress } = useScroll() // 0 → 1 по всей странице
const scaleX = useSpring(scrollYProgress) // сглажено пружиной
<motion.div style={{ scaleX }} className="fixed inset-x-0 top-0 h-1 origin-left bg-accent" /> // полоса прогресса
const y = useTransform(scrollYProgress, [0, 1], [0, -200]) // параллаксuseTransform = интерполяция: отображает входной диапазон на выходной.
Пружина или tween
Tween (duration + ease) |
Пружина | |
|---|---|---|
| Длительность | фиксированная | следует из физики |
| Ощущение | контролируемое, точное | естественное, «живое» |
| Прерывание на середине | может дёрнуться | продолжает плавно, сохраняет скорость |
| Когда | прозрачность, цвет, затухания | движение, размер, перетаскивание, жесты |
Коротко
motion.div+initial/animate/exit;AnimatePresenceдля ухода.- Variants +
stagger()для списков;whileInViewдля появления при прокрутке. layout/layoutIdдля переходов раскладки;useScroll+useTransformдля прокрутки; пружины для движения.