Back to blog
Mastering Framer Motion: Advanced Animation Patterns
AnimationFeb 14, 202610 min read

Mastering Framer Motion: Advanced Animation Patterns

Go beyond basic fade-ins. Learn layout animations, shared element transitions, and gesture-driven interactions with Framer Motion.


Most developers discover Framer Motion through simple fade-in animations and stop there. But the library has a much deeper set of capabilities — layout animations, shared element transitions, gesture-driven interactions, and orchestration tools that can make your UI feel genuinely alive. Let's go beyond the basics.

Layout Animations

The layout prop is one of Framer Motion's most powerful features. Add it to any element and Framer Motion will automatically animate it whenever its size or position changes — no keyframes needed.

// This list item will smoothly animate when items are added/removed
function TodoItem({ todo, onRemove }) {
  return (
    <motion.li layout className="todo-item">
      {todo.text}
      <button onClick={() => onRemove(todo.id)}>Remove</button>
    </motion.li>
  )
}

Wrap the parent in <AnimatePresence> to also animate items as they leave the DOM:

<AnimatePresence>
  {todos.map(todo => (
    <motion.li
      key={todo.id}
      layout
      initial={{ opacity: 0, y: -10 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, x: -20 }}
    >
      {todo.text}
    </motion.li>
  ))}
</AnimatePresence>

Shared Element Transitions with layoutId

The layoutId prop lets you create seamless transitions between two separate elements — like expanding a card into a modal. Framer Motion treats elements with the same layoutId as the same element and animates between their positions and sizes.

// Card in the grid
function ProjectCard({ project, onClick }) {
  return (
    <motion.div layoutId={`card-${project.id}`} onClick={onClick}>
      <motion.img layoutId={`image-${project.id}`} src={project.image} />
      <motion.h3 layoutId={`title-${project.id}`}>{project.title}</motion.h3>
    </motion.div>
  )
}

// Expanded modal — same layoutIds, different position/size
function ProjectModal({ project, onClose }) {
  return (
    <motion.div layoutId={`card-${project.id}`} className="modal">
      <motion.img layoutId={`image-${project.id}`} src={project.image} />
      <motion.h3 layoutId={`title-${project.id}`}>{project.title}</motion.h3>
      <p>{project.description}</p>
    </motion.div>
  )
}

Gesture-Driven Interactions

Framer Motion has first-class support for drag, hover, tap, and pan gestures. These integrate directly with the animation system.

// Draggable card with snap-back
<motion.div
  drag
  dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
  dragElastic={0.2}
  whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
>
  Drag me
</motion.div>

Scroll-Driven Animations with useScroll

The useScroll hook gives you a reactive scroll progress value you can map to any animatable property using useTransform.

function ParallaxHero() {
  const { scrollY } = useScroll()
  const y = useTransform(scrollY, [0, 500], [0, -150])
  const opacity = useTransform(scrollY, [0, 300], [1, 0])

  return (
    <section>
      <motion.div style={{ y, opacity }} className="hero-bg" />
      <motion.h1 style={{ y: useTransform(scrollY, [0, 300], [0, -50]) }}>
        Hello World
      </motion.h1>
    </section>
  )
}

Orchestrating Complex Sequences with variants

Variants let you define named animation states and propagate them through a component tree. The parent controls when children animate using staggerChildren and delayChildren.

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
      delayChildren: 0.3,
    },
  },
}

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 },
}

function AnimatedList({ items }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="show">
      {items.map(i => (
        <motion.li key={i.id} variants={item}>
          {i.text}
        </motion.li>
      ))}
    </motion.ul>
  )
}

Performance Tips

  • Animate only transform and opacity — they don't trigger layout recalculation
  • Use will-change: transform sparingly on elements that animate frequently
  • Prefer useMotionValue + useTransform over state-driven animations for scroll effects — they bypass React re-renders entirely
  • Use layout="position" instead of layout when you only need position animation, not size

Wrapping Up

Framer Motion rewards investment. The more you understand its model — variants, layout animations, motion values — the more expressive and performant your animations become. Start with one pattern from this post and integrate it into a real project. That's the fastest way to make it stick.