# Motion (Framer Motion) Patterns
React animation patterns using Motion library (formerly Framer Motion).
## Table of Contents
1. [Setup](#setup)
2. [Basic Animations](#basic-animations)
3. [Scroll Animations](#scroll-animations)
4. [Page Transitions](#page-transitions)
5. [Text Animations](#text-animations)
6. [Gestures](#gestures)
7. [Layout Animations](#layout-animations)
8. [Exit Animations](#exit-animations)
## Setup
```bash
npm install motion
```
```jsx
import { motion, useScroll, useTransform, AnimatePresence } from 'motion/react'
```
## Basic Animations
### Simple Animation
```jsx
Content
```
### Variants Pattern
```jsx
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
}
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5, ease: 'easeOut' }
}
}
function List({ items }) {
return (
{items.map(item => (
{item.name}
))}
)
}
```
### While In View
```jsx
Animates when scrolled into view
```
## Scroll Animations
### Scroll Progress
```jsx
import { motion, useScroll, useTransform } from 'motion/react'
function ScrollProgress() {
const { scrollYProgress } = useScroll()
return (
)
}
```
### Parallax Effect
```jsx
function ParallaxHero() {
const { scrollY } = useScroll()
const y = useTransform(scrollY, [0, 500], [0, 150])
const opacity = useTransform(scrollY, [0, 300], [1, 0])
return (
Hero Title
)
}
```
### Element-Based Scroll
```jsx
function ScrollSection() {
const ref = useRef(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start end', 'end start']
})
const scale = useTransform(scrollYProgress, [0, 0.5, 1], [0.8, 1, 0.8])
const opacity = useTransform(scrollYProgress, [0, 0.3, 0.7, 1], [0, 1, 1, 0])
return (
Content
)
}
```
### Scroll Velocity
```jsx
import { useScroll, useVelocity, useTransform, motion } from 'motion/react'
function VelocityText() {
const { scrollY } = useScroll()
const scrollVelocity = useVelocity(scrollY)
const skewY = useTransform(scrollVelocity, [-1000, 0, 1000], [-3, 0, 3])
return (
Velocity Skew
)
}
```
## Page Transitions
### Basic Page Transition
```jsx
// layout.jsx
import { AnimatePresence } from 'motion/react'
function Layout({ children }) {
return (
{children}
)
}
// page.jsx
const pageVariants = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -20 }
}
function Page() {
return (
Content
)
}
```
### Overlay Transition
```jsx
const overlayVariants = {
initial: { scaleY: 0 },
animate: {
scaleY: 1,
transition: { duration: 0.5, ease: [0.76, 0, 0.24, 1] }
},
exit: {
scaleY: 0,
transition: { duration: 0.5, ease: [0.76, 0, 0.24, 1], delay: 0.2 }
}
}
function PageTransition({ children }) {
return (
<>
{children}
>
)
}
```
### Shared Layout Animation
```jsx
function CardGrid({ items, selectedId, setSelectedId }) {
return (
<>
{items.map(item => (
setSelectedId(item.id)}
>
{item.title}
))}
{selectedId && (
setSelectedId(null)}
>
{items.find(i => i.id === selectedId).title}
)}
>
)
}
```
## Text Animations
### Character-by-Character Reveal
```jsx
function AnimatedText({ text }) {
const chars = text.split('')
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.02 }
}
}
const child = {
hidden: { opacity: 0, y: 50, rotateX: -90 },
visible: {
opacity: 1,
y: 0,
rotateX: 0,
transition: { type: 'spring', damping: 12 }
}
}
return (
{chars.map((char, i) => (
{char === ' ' ? '\u00A0' : char}
))}
)
}
```
### Word-by-Word Animation
```jsx
function AnimatedWords({ text }) {
const words = text.split(' ')
return (
{words.map((word, i) => (
{word}{' '}
))}
)
}
```
### Line Mask Reveal
```jsx
function MaskReveal({ children }) {
return (
{children}
)
}
```
## Gestures
### Magnetic Button
```jsx
function MagneticButton({ children }) {
const ref = useRef(null)
const [position, setPosition] = useState({ x: 0, y: 0 })
const handleMouse = (e) => {
const { clientX, clientY } = e
const { left, top, width, height } = ref.current.getBoundingClientRect()
const x = (clientX - left - width / 2) * 0.3
const y = (clientY - top - height / 2) * 0.3
setPosition({ x, y })
}
const reset = () => setPosition({ x: 0, y: 0 })
return (
{children}
)
}
```
### Hover Effects
```jsx
function HoverCard() {
return (
)
}
```
### Drag
```jsx
function DraggableCard() {
return (
Drag me
)
}
```
## Layout Animations
### Reorder List
```jsx
import { Reorder } from 'motion/react'
function ReorderList() {
const [items, setItems] = useState([1, 2, 3, 4])
return (
{items.map(item => (
{item}
))}
)
}
```
### Accordion
```jsx
function Accordion({ title, children }) {
const [isOpen, setIsOpen] = useState(false)
return (
setIsOpen(!isOpen)}>
{title}
▼
{isOpen && (
{children}
)}
)
}
```
## Exit Animations
### Exit with AnimatePresence
```jsx
function Modal({ isOpen, onClose, children }) {
return (
{isOpen && (
<>
{children}
>
)}
)
}
```
## Easing Reference
```jsx
// Custom cubic bezier (recommended)
transition: { ease: [0.76, 0, 0.24, 1] } // ease-in-out
transition: { ease: [0.16, 1, 0.3, 1] } // ease-out (smooth)
transition: { ease: [0.87, 0, 0.13, 1] } // dramatic
// Spring (for bouncy feel)
transition: { type: 'spring', stiffness: 300, damping: 20 }
// Tween (for precise control)
transition: { type: 'tween', duration: 0.5, ease: 'easeOut' }
```
## Performance Tips
1. Use `layout` prop sparingly - it can be expensive
2. Avoid animating `width`/`height` - use `scale` instead
3. Use `will-change: transform` via CSS for heavy animations
4. Use `layoutId` only when necessary
5. Memoize variants objects
6. Use `useReducedMotion` hook for accessibility
```jsx
import { useReducedMotion } from 'motion/react'
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
)
}
```