javascriptroom guide

Tailoring Animations in React with the Framer Motion Library

In the world of modern web development, animations are no longer just decorative—they’re a critical part of user experience (UX). Well-crafted animations guide users, provide feedback, and make interfaces feel responsive and intuitive. However, implementing smooth, performant animations in React can be challenging with vanilla CSS or even basic JavaScript libraries. Enter **Framer Motion**—a powerful, declarative animation library designed specifically for React that simplifies creating complex animations with minimal code. Framer Motion abstracts the complexity of low-level animation APIs (like CSS transitions or Web Animations) and provides a React-friendly interface to define animations. Whether you need simple hover effects, complex sequence orchestration, or dynamic layout transitions, Framer Motion has you covered. In this blog, we’ll dive deep into tailoring animations in React with Framer Motion, covering everything from basic setup to advanced techniques, performance best practices, and real-world examples.

Table of Contents

  1. Getting Started: Installation & Setup
  2. Core Concepts: Motion Components & Basic Animation
  3. Variants: Reusable Animation Definitions
  4. Transitions: Controlling Animation Timing & Easing
  5. Interactive Gestures: Hover, Tap, & Drag
  6. Layout Animations: Automating Size & Position Changes
  7. Animation Orchestration: Stagger, Sequence, & Parallel
  8. Performance Best Practices
  9. Real-World Example: Animated Modal
  10. Conclusion
  11. References

1. Getting Started: Installation & Setup

Before we dive into animations, let’s set up Framer Motion in a React project.

Step 1: Install Framer Motion

First, install the library via npm or yarn:

# Using npm  
npm install framer-motion  

# Using yarn  
yarn add framer-motion  

Step 2: Basic Setup

Framer Motion works by wrapping React elements with its motion component. Import motion from framer-motion and use it to animate any HTML or SVG element.

Let’s start with a simple “Hello World” animation: a div that fades in and slides up when the component mounts.

import { motion } from "framer-motion";  

function App() {  
  return (  
    <motion.div  
      initial={{ opacity: 0, y: 20 }} // Start state: invisible, 20px below  
      animate={{ opacity: 1, y: 0 }} // End state: visible, in place  
      transition={{ duration: 0.5 }} // Animation timing: 500ms  
    >  
      Hello, Framer Motion!  
    </motion.div>  
  );  
}  

export default App;  

In this example:

  • initial: Defines the starting state of the animation (before it runs).
  • animate: Defines the target state (what the element animates to).
  • transition: Controls timing, easing, and other animation behavior.

2. Core Concepts: Motion Components & Basic Animation

At the heart of Framer Motion is the motion component. It wraps standard HTML/SVG elements (like div, span, svg, etc.) and adds animation-specific props.

Key Props for Basic Animation

PropPurpose
initialStarting state of the animation (e.g., { opacity: 0, scale: 0.8 }).
animateTarget state to animate to (e.g., { opacity: 1, scale: 1 }).
exitState for when the component unmounts (requires AnimatePresence).
transitionConfigures duration, easing, delay, and physics (e.g., { duration: 0.3, ease: "easeOut" }).

Example: Fade & Scale Animation

Let’s animate a card that fades in and scales up on mount:

import { motion } from "framer-motion";  

const Card = () => (  
  <motion.div  
    className="card"  
    style={{ width: 200, height: 150, padding: 20, background: "#fff", borderRadius: 8, boxShadow: "0 4px 6px rgba(0,0,0,0.1)" }}  
    initial={{ opacity: 0, scale: 0.9 }} // Start: transparent, slightly scaled down  
    animate={{ opacity: 1, scale: 1 }} // End: opaque, full size  
    transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1] }} // Smooth "easeOut" curve  
  >  
    Animated Card  
  </motion.div>  
);  

export default Card;  

Here, ease: [0.25, 0.1, 0.25, 1] uses a cubic-bezier curve for a natural,缓急有致 (gradual acceleration/deceleration) feel.

3. Variants: Reusable Animation Definitions

As animations grow more complex, repeating initial/animate props across components becomes tedious. Variants solve this by letting you define reusable animation states.

Defining Variants

A variant is an object where keys represent animation states (e.g., hidden, visible), and values are the properties to animate:

const fadeInVariant = {  
  hidden: { opacity: 0, y: 20 }, // Start state  
  visible: { opacity: 1, y: 0 }  // End state  
};  

Using Variants

Pass the variant to the motion component and reference the states via initial, animate, or exit:

import { motion } from "framer-motion";  

const fadeInVariant = {  
  hidden: { opacity: 0, y: 20 },  
  visible: { opacity: 1, y: 0 }  
};  

const FadeInElement = () => (  
  <motion.div  
    variants={fadeInVariant}  
    initial="hidden" // Start in "hidden" state  
    animate="visible" // Animate to "visible" state  
    transition={{ duration: 0.5 }}  
  >  
    I fade in!  
  </motion.div>  
);  

Variants promote reusability—you can share them across components or even import them from a separate file (e.g., animations/variants.js).

4. Transitions: Controlling Animation Timing & Easing

The transition prop lets you fine-tune how animations behave. Beyond duration and ease, you can control delays, physics-based motion, and more.

Common Transition Options

OptionDescription
durationLength of the animation in seconds (e.g., 0.5 for 500ms).
delayDelay before the animation starts (e.g., 0.2 for 200ms delay).
easeEasing function (e.g., "linear", "easeInOut", or a cubic-bezier array).
typeAnimation type: "tween" (default, linear interpolation) or "spring" (physics-based).
stiffnessFor spring animations: Higher values = stiffer (less bouncy) motion.
dampingFor spring animations: Higher values = less oscillation.

Example: Spring Animation

For natural, physics-driven motion (e.g., a button that “springs” when clicked), use type: "spring":

<motion.button  
  whileTap={{ scale: 0.95 }} // Animate on tap  
  transition={{ type: "spring", stiffness: 300, damping: 20 }}  
>  
  Click Me  
</motion.button>  

Here, stiffness: 300 makes the spring tight, and damping: 20 reduces bounce, creating a snappy, responsive feel.

5. Interactive Gestures: Hover, Tap, & Drag

Framer Motion simplifies adding interactive animations with built-in gesture props. These props trigger animations in response to user input.

Key Gesture Props

PropTrigger
whileHoverAnimate when the user hovers over the element.
whileTapAnimate when the user clicks/taps the element.
whileDragAnimate while the user drags the element (requires drag prop).

Example: Interactive Button

import { motion } from "framer-motion";  

const InteractiveButton = () => (  
  <motion.button  
    className="btn"  
    whileHover={{ scale: 1.05, boxShadow: "0 8px 16px rgba(0,0,0,0.2)" }} // Hover: scale up, shadow  
    whileTap={{ scale: 0.98 }} // Tap: scale down slightly  
    transition={{ type: "spring", stiffness: 400, damping: 10 }}  
    style={{ padding: "12px 24px", border: "none", borderRadius: 4, background: "#007bff", color: "white", cursor: "pointer" }}  
  >  
    Hover or Tap Me  
  </motion.button>  
);  

This button feels alive: it grows subtly on hover and “presses” down on tap, mimicking physical buttons.

6. Layout Animations: Automating Size & Position Changes

One of Framer Motion’s most powerful features is layout animations—automatically animating elements when their size or position changes (e.g., adding items to a list, resizing a panel).

The layout Prop

Add layout (or layoutId for cross-component transitions) to a motion element, and Framer Motion will animate its position/size when it updates:

import { motion, AnimatePresence } from "framer-motion";  
import { useState } from "react";  

const LayoutAnimationExample = () => {  
  const [showDetails, setShowDetails] = useState(false);  

  return (  
    <div>  
      <button onClick={() => setShowDetails(!showDetails)}>  
        Toggle Details  
      </button>  
      <motion.div  
        layout // Animate size/position changes  
        style={{  
          background: "#f0f0f0",  
          padding: showDetails ? "20px" : "10px",  
          borderRadius: 8  
        }}  
      >  
        Base Content  
        {showDetails && <p>Extra details here!</p>}  
      </motion.div>  
    </div>  
  );  
};  

When showDetails toggles, the div smoothly animates its height to fit the new content—no manual calculations required!

Cross-Component Layout Transitions with layoutId

Use layoutId to animate elements between different components (e.g., a card expanding into a full-page view). Both elements must share the same layoutId:

// ListItem.jsx  
const ListItem = ({ item }) => (  
  <motion.div layoutId={item.id} className="list-item">  
    {item.title}  
  </motion.div>  
);  

// DetailView.jsx  
const DetailView = ({ item }) => (  
  <motion.div layoutId={item.id} className="detail-view">  
    <h1>{item.title}</h1>  
    <p>{item.description}</p>  
  </motion.div>  
);  

When navigating from ListItem to DetailView, Framer Motion animates the element’s size, position, and even styling (e.g., borderRadius) between the two components.

7. Animation Orchestration: Stagger, Sequence, & Parallel

For complex UIs (e.g., grids, lists), you’ll often want to orchestrate animations—staggering delays, chaining sequences, or running animations in parallel.

Staggering Animations

Use staggerChildren in a parent variant to stagger animations for child elements. Pair it with delayChildren to control the initial delay:

import { motion } from "framer-motion";  

// Parent variant with stagger configuration  
const parentVariant = {  
  hidden: { opacity: 0 },  
  visible: {  
    opacity: 1,  
    transition: {  
      staggerChildren: 0.1, // Stagger child animations by 100ms  
      delayChildren: 0.2 // Start staggering after 200ms  
    }  
  }  
};  

// Child variant (reused for each item)  
const childVariant = {  
  hidden: { opacity: 0, y: 20 },  
  visible: { opacity: 1, y: 0 }  
};  

const StaggeredList = () => (  
  <motion.ul variants={parentVariant} initial="hidden" animate="visible">  
    {["Item 1", "Item 2", "Item 3", "Item 4"].map((item, index) => (  
      <motion.li  
        key={index}  
        variants={childVariant}  
        transition={{ duration: 0.3 }}  
        style={{ listStyle: "none", padding: "8px", margin: "4px", background: "#f0f0f0" }}  
      >  
        {item}  
      </motion.li>  
    ))}  
  </motion.ul>  
);  

Here, the list items fade in one after another with a 100ms delay between them.

Sequences & Parallel Animations

Use the when transition option to chain animations (run one after another) or run them in parallel:

const sequenceVariant = {  
  first: { opacity: 0 },  
  second: { opacity: 1, transition: { when: "beforeChildren" } }, // Run before children  
  third: { opacity: 1, transition: { when: "afterChildren" } } // Run after children  
};  

For more control, use withSequence or withParallel from framer-motion:

import { motion, withSequence, withParallel } from "framer-motion";  

const complexTransition = {  
  x: withSequence(0, 100, 50), // Animate x: 0 → 100 → 50 (sequence)  
  opacity: withParallel(0, 1) // Animate opacity: 0 → 1 (parallel with x)  
};  

8. Performance Best Practices

Framer Motion is optimized for performance, but poor implementation can still cause jank. Follow these tips:

1. Animate transform and opacity Only

Browsers optimize transform (e.g., translate, scale) and opacity animations via hardware acceleration. Avoid animating properties like width, height, or margin—these trigger expensive layout recalculations.

2. Use willChange Sparingly

While willChange: "transform" hints to the browser to prepare for animation, overusing it wastes resources. Let Framer Motion handle this automatically where possible.

3. Avoid Layout Thrashing

If reading layout properties (e.g., offsetHeight) during animations, batch reads/writes to prevent forced synchronous layouts.

4. Leverage AnimatePresence for Exit Animations

For components that unmount (e.g., modals, tabs), wrap them in AnimatePresence to ensure exit animations complete before removal:

import { motion, AnimatePresence } from "framer-motion";  
import { useState } from "react";  

const Modal = ({ isOpen, onClose }) => (  
  <AnimatePresence>  
    {isOpen && (  
      <motion.div  
        key="modal" // Required for AnimatePresence to track the component  
        initial={{ opacity: 0 }}  
        animate={{ opacity: 1 }}  
        exit={{ opacity: 0 }}  
        onClick={onClose}  
        style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)" }}  
      >  
        <motion.div  
          initial={{ scale: 0.9 }}  
          animate={{ scale: 1 }}  
          exit={{ scale: 0.9 }}  
          onClick={(e) => e.stopPropagation()}  
          style={{ background: "white", padding: 20, margin: "100px auto", maxWidth: 500 }}  
        >  
          Modal Content  
          <button onClick={onClose}>Close</button>  
        </motion.div>  
      </motion.div>  
    )}  
  </AnimatePresence>  
);  

9. Real-World Example: Animated Modal

Let’s combine everything we’ve learned to build a production-ready animated modal. This modal will:

  • Fade in a semi-transparent backdrop.
  • Scale up the modal content from 90% to 100% size.
  • Animate out smoothly when closed.

Step 1: Define Variants

// animations/modalVariants.js  
export const backdropVariants = {  
  hidden: { opacity: 0 },  
  visible: { opacity: 1 },  
  exit: { opacity: 0 }  
};  

export const modalContentVariants = {  
  hidden: { scale: 0.9, opacity: 0 },  
  visible: { scale: 1, opacity: 1 },  
  exit: { scale: 0.9, opacity: 0 }  
};  

Step 2: Build the Modal Component

import { motion, AnimatePresence } from "framer-motion";  
import { useState } from "react";  
import { backdropVariants, modalContentVariants } from "./animations/modalVariants";  

const AnimatedModal = () => {  
  const [isOpen, setIsOpen] = useState(false);  

  return (  
    <div>  
      <button onClick={() => setIsOpen(true)}>Open Modal</button>  

      <AnimatePresence>  
        {isOpen && (  
          <>  
            {/* Backdrop */}  
            <motion.div  
              variants={backdropVariants}  
              initial="hidden"  
              animate="visible"  
              exit="exit"  
              transition={{ duration: 0.3 }}  
              style={{  
                position: "fixed",  
                top: 0,  
                left: 0,  
                right: 0,  
                bottom: 0,  
                background: "rgba(0, 0, 0, 0.5)",  
                zIndex: 100  
              }}  
              onClick={() => setIsOpen(false)} // Close on backdrop click  
            />  

            {/* Modal Content */}  
            <motion.div  
              variants={modalContentVariants}  
              initial="hidden"  
              animate="visible"  
              exit="exit"  
              transition={{ type: "spring", stiffness: 300, damping: 20 }}  
              style={{  
                position: "fixed",  
                top: "50%",  
                left: "50%",  
                transform: "translate(-50%, -50%)",  
                background: "white",  
                padding: "2rem",  
                borderRadius: "8px",  
                zIndex: 101,  
                width: "90%",  
                maxWidth: "500px"  
              }}  
              onClick={(e) => e.stopPropagation()} // Prevent closing when clicking inside modal  
            >  
              <h2>Animated Modal</h2>  
              <p>This modal fades in and scales up smoothly!</p>  
              <button onClick={() => setIsOpen(false)} style={{ marginTop: "1rem" }}>  
                Close  
              </button>  
            </motion.div>  
          </>  
        )}  
      </AnimatePresence>  
    </div>  
  );  
};  

export default AnimatedModal;  

10. Conclusion

Framer Motion empowers React developers to create stunning, performant animations with minimal effort. By leveraging its declarative API, reusable variants, and powerful features like layout animations and gesture support, you can elevate your UI from static to dynamic.

Start small: experiment with basic motion components and variants, then gradually explore advanced features like orchestration and cross-component transitions. With Framer Motion, the only limit is your creativity!

11. References