Skip to content
Go back

Using Framer Motion for Complex React Animations

Using Framer Motion for Complex React Animations

Introduction

Framer Motion brings powerful animation capabilities to React with declarative APIs. This guide covers advanced animation techniques for modern web applications.

Prerequisites

Step 1: Install Framer Motion

npm install framer-motion

Step 2: Basic Motion Components

Create components/BasicAnimations.tsx:

import { motion } from 'framer-motion';

export default function BasicAnimations() {
  return (
    <div className="p-8 space-y-8">
      {/* Simple fade in */}
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ duration: 0.5 }}
        className="bg-blue-500 text-white p-4 rounded"
      >
        Fade In Animation
      </motion.div>

      {/* Slide and scale */}
      <motion.div
        initial={{ x: -100, scale: 0.8 }}
        animate={{ x: 0, scale: 1 }}
        transition={{ type: "spring", stiffness: 100 }}
        className="bg-green-500 text-white p-4 rounded"
      >
        Slide & Scale Animation
      </motion.div>

      {/* Rotating card */}
      <motion.div
        whileHover={{ rotate: 180, scale: 1.1 }}
        whileTap={{ scale: 0.9 }}
        transition={{ type: "spring", stiffness: 300 }}
        className="bg-purple-500 text-white p-4 rounded cursor-pointer"
      >
        Hover & Tap Animation
      </motion.div>
    </div>
  );
}

Step 3: Layout Animations

Create components/LayoutAnimations.tsx:

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

export default function LayoutAnimations() {
  const [isExpanded, setIsExpanded] = useState(false);
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  const removeItem = (id: number) => {
    setItems(items.filter(item => item !== id));
  };

  return (
    <div className="p-8 space-y-8">
      {/* Expandable card */}
      <motion.div
        layout 
        className="bg-white border rounded-lg shadow-md cursor-pointer overflow-hidden"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <motion.div layout className="p-6">
          <motion.h3 layout className="text-xl font-semibold mb-2">
            Expandable Card
          </motion.h3>
          <motion.p layout className="text-gray-600">
            Click to expand or collapse
          </motion.p>
        </motion.div>

        <AnimatePresence>
          {isExpanded && (
            <motion.div
              initial={{ opacity: 0, height: 0 }}
              animate={{ opacity: 1, height: "auto" }}
              exit={{ opacity: 0, height: 0 }}
              transition={{ duration: 0.3 }}
              className="px-6 pb-6"
            >
              <p className="text-gray-700">
                This is additional content that appears when expanded.
                The layout animation handles the smooth transition.
              </p>
            </motion.div>
          )}
        </AnimatePresence>
      </motion.div>

      {/* Animated list */}
      <div>
        <h3 className="text-xl font-semibold mb-4">Animated List</h3>
        <motion.div layout className="space-y-2">
          <AnimatePresence>
            {items.map(item => (
              <motion.div
                key={item}
                layout
                initial={{ opacity: 0, y: -20 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, x: -100 }}
                whileHover={{ scale: 1.02, x: 10 }}
                className="bg-gray-100 p-4 rounded cursor-pointer flex justify-between items-center"
                onClick={() => removeItem(item)}
              >
                <span>Item {item}</span>
                <span className="text-red-500 text-sm">Click to remove</span>
              </motion.div>
            ))}
          </AnimatePresence>
        </motion.div>
      </div>
    </div>
  );
}

Step 4: Gesture Animations

Create components/GestureAnimations.tsx:

import { motion, useMotionValue, useTransform } from 'framer-motion';

export default function GestureAnimations() {
  const x = useMotionValue(0);
  const rotate = useTransform(x, [-100, 100], [-45, 45]);
  const opacity = useTransform(x, [-100, -50, 0, 50, 100], [0, 1, 1, 1, 0]);

  return (
    <div className="p-8 space-y-8">
      <h2 className="text-2xl font-bold">Gesture Animations</h2>

      {/* Draggable card */}
      <div className="h-64 bg-gray-100 rounded-lg relative overflow-hidden">
        <motion.div
          drag
          dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
          dragElastic={0.2}
          whileDrag={{ scale: 1.1, rotate: 5 }}
          className="absolute top-1/2 left-1/2 w-24 h-32 bg-blue-500 rounded-lg shadow-lg cursor-grab active:cursor-grabbing flex items-center justify-center text-white font-semibold"
          style={{ x: "-50%", y: "-50%" }}
        >
          Drag Me
        </motion.div>
      </div>

      {/* Swipeable card */}
      <motion.div
        style={{ x, rotate, opacity }}
        drag="x"
        dragConstraints={{ left: 0, right: 0 }}
        dragElastic={1}
        onDragEnd={(event, info) => {
          if (info.offset.x > 100) {
            // Swiped right
            console.log('Swiped right!');
          } else if (info.offset.x < -100) {
            // Swiped left
            console.log('Swiped left!');
          }
        }}
        className="w-80 h-48 bg-gradient-to-br from-pink-400 to-red-500 rounded-xl shadow-lg cursor-grab active:cursor-grabbing flex items-center justify-center text-white text-xl font-semibold mx-auto"
      >
        Swipe Left or Right
      </motion.div>
    </div>
  );
}

Step 5: Complex Page Transitions

Create components/PageTransitions.tsx:

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

const pages = [
  { id: 1, title: 'Home', color: 'bg-blue-500' },
  { id: 2, title: 'About', color: 'bg-green-500' },
  { id: 3, title: 'Contact', color: 'bg-purple-500' },
];

const pageVariants = {
  initial: { opacity: 0, x: "-100vw" },
  in: { opacity: 1, x: 0 },
  out: { opacity: 0, x: "100vw" }
};

const pageTransition = {
  type: "tween",
  ease: "anticipate",
  duration: 0.5
};

export default function PageTransitions() {
  const [currentPage, setCurrentPage] = useState(0);

  return (
    <div className="h-96 relative overflow-hidden rounded-lg">
      {/* Navigation */}
      <div className="absolute top-4 left-4 z-20 space-x-2">
        {pages.map((page, index) => (
          <button
            key={page.id}
            onClick={() => setCurrentPage(index)}
            className={`px-4 py-2 rounded ${
              currentPage === index
                ? 'bg-white text-gray-800'
                : 'bg-white bg-opacity-20 text-white'
            }`}
          >
            {page.title}
          </button>
        ))}
      </div>

      {/* Page content */}
      <AnimatePresence mode="wait">
        <motion.div
          key={currentPage}
          initial="initial"
          animate="in"
          exit="out"
          variants={pageVariants}
          transition={pageTransition}
          className={`absolute inset-0 flex items-center justify-center text-white text-4xl font-bold ${pages[currentPage].color}`}
        >
          {pages[currentPage].title} Page
        </motion.div>
      </AnimatePresence>
    </div>
  );
}

Step 6: Staggered Animations

Create components/StaggeredAnimations.tsx:

import { motion } from 'framer-motion';

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

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

export default function StaggeredAnimations() {
  const items = Array.from({ length: 12 }, (_, i) => i + 1);

  return (
    <div className="p-8">
      <h2 className="text-2xl font-bold mb-8">Staggered Grid Animation</h2>

      <motion.div
        variants={container}
        initial="hidden"
        animate="show"
        className="grid grid-cols-4 gap-4"
      >
        {items.map(num => (
          <motion.div
            key={num}
            variants={item}
            whileHover={{ scale: 1.05 }}
            whileTap={{ scale: 0.95 }}
            className="aspect-square bg-gradient-to-br from-blue-400 to-purple-500 rounded-lg flex items-center justify-center text-white font-bold text-2xl cursor-pointer"
          >
            {num}
          </motion.div>
        ))}
      </motion.div>
    </div>
  );
}

Step 7: Main Demo Component

import BasicAnimations from '@/components/BasicAnimations';
import LayoutAnimations from '@/components/LayoutAnimations';
import GestureAnimations from '@/components/GestureAnimations';
import PageTransitions from '@/components/PageTransitions';
import StaggeredAnimations from '@/components/StaggeredAnimations';

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-50">
      <div className="container mx-auto py-12">
        <h1 className="text-4xl font-bold text-center mb-12">
          Framer Motion Animations
        </h1>

        <div className="space-y-16">
          <BasicAnimations />
          <LayoutAnimations />
          <GestureAnimations />
          <PageTransitions />
          <StaggeredAnimations />
        </div>
      </div>
    </main>
  );
}

Summary

Framer Motion provides declarative animation APIs with layout animations, gestures, and complex transitions. Use motion components, AnimatePresence, and variant patterns for production-ready animations.


Share this post on:

Previous Post
Optimizing React App Performance with Code Splitting
Next Post
Building a Drag-and-Drop Dashboard in React