webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
Animations · Lesson 2 of 4

Motion for React

motion.div, initial/animate/exit, AnimatePresence, variants and stagger, layout, scroll, springs.

Updated

What Motion is

Motion (formerly Framer Motion) is the most used animation library in React. The idea: you replace an HTML tag with its motion. version and describe the states, not the steps — Motion computes the animation between them.

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.* components are Client Components — the file has 'use client'. You can keep the page as a Server Component and animate only small pieces.

The basic props

Prop What it does
initial the starting state (on mount)
animate the target state — when it changes, Motion animates toward it
exit the state on unmount (needs AnimatePresence)
transition duration, ease, delay, or type: 'spring' with stiffness / damping / bounce
whileHover, whileTap, whileFocus temporary states on interaction
whileInView a state when the element enters the screen (+ viewport={{ once: true }})
layout automatically animates size / position changes in the layout
layoutId two different elements with the same id "transform" into each other

Exits: AnimatePresence

React removes an element from the DOM immediately when it's no longer rendered — there's no time for an animation. AnimatePresence keeps it until exit finishes:

<AnimatePresence>
  {open && (
    <motion.div key="panel" initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} />
  )}
</AnimatePresence>

Variants and stagger

Variants = named states, defined once and propagated to the children. Ideal for lists:

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>

The children don't need initial / animate — they inherit the state names from the parent.

Layout animations — the "wow" effect in one line

<motion.div layout className={expanded ? 'w-full' : 'w-40'} />     // the size animates automatically
{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>
))}

The indicator "slides" from one tab to the next — without any position calculation.

Scroll

const { scrollYProgress } = useScroll()                      // 0 → 1 over the whole page
const scaleX = useSpring(scrollYProgress)                     // smoothed with a spring
<motion.div style={{ scaleX }} className="fixed inset-x-0 top-0 h-1 origin-left bg-accent" />   // a progress bar

const y = useTransform(scrollYProgress, [0, 1], [0, -200])   // parallax

useTransform = interpolation: it maps an input range onto an output range.

Spring vs tween

Tween (duration + ease) Spring
Duration fixed comes from physics
Feel controlled, precise natural, "alive"
Interrupted midway can jump continues smoothly, keeps its velocity
When opacity, color, fades movement, size, drag, gestures

Summary

  • motion.div + initial / animate / exit; AnimatePresence for exits.
  • Variants + stagger() for lists; whileInView for reveals on scroll.
  • layout / layoutId for layout transitions; useScroll + useTransform for scroll; springs for movement.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.