React.js Fundamentals

Animations in React

15 min Lesson 33 of 40

Animations in React

Animations bring your React applications to life by providing visual feedback and improving user experience. Learn how to implement animations using CSS transitions and the powerful Framer Motion library.

CSS Transitions and Animations

Start with basic CSS transitions for simple animations:

import { useState } from 'react'; import './Button.css'; function AnimatedButton() { const [isHovered, setIsHovered] = useState(false); return ( <button className={`animated-button ${isHovered ? 'hovered' : ''}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > Hover Me </button> ); }

CSS file (Button.css):

.animated-button { padding: 12px 24px; background: #007bff; color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; transform: scale(1); } .animated-button.hovered { background: #0056b3; transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); } /* Fade in animation */ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .fade-in { animation: fadeIn 0.5s ease-out; }
Note: CSS transitions are great for simple animations. For more complex animations with multiple states and sequences, consider using a library like Framer Motion.

Installing and Setting Up Framer Motion

Framer Motion is a production-ready motion library for React. Install it with:

npm install framer-motion

Basic usage example:

import { motion } from 'framer-motion'; function FadeInBox() { return ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} style={{ width: 200, height: 200, background: '#007bff', borderRadius: 8, }} > I fade in! </motion.div> ); }
Tip: Framer Motion works with any HTML or SVG element. Just replace the element tag with motion.element (e.g., motion.div, motion.button, motion.svg).

Hover, Tap, and Focus Animations

Add interactive animations with gesture props:

import { motion } from 'framer-motion'; function InteractiveCard() { return ( <motion.div className="card" whileHover={{ scale: 1.05, boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)', }} whileTap={{ scale: 0.95 }} whileFocus={{ outline: '2px solid #007bff' }} transition={{ type: 'spring', stiffness: 300 }} style={{ width: 300, padding: 20, background: 'white', borderRadius: 8, cursor: 'pointer', }} > <h3>Interactive Card</h3> <p>Hover, tap, or focus on me!</p> </motion.div> ); } // Button with multiple gesture animations function AnimatedButton() { return ( <motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} transition={{ type: 'spring', stiffness: 400, damping: 17 }} style={{ padding: '12px 24px', background: '#28a745', color: 'white', border: 'none', borderRadius: 8, cursor: 'pointer', fontSize: 16, }} > Click Me </motion.button> ); }

AnimatePresence for Exit Animations

Animate components when they're removed from the DOM:

import { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; function NotificationList() { const [notifications, setNotifications] = useState([ { id: 1, message: 'Success! Your changes have been saved.' }, { id: 2, message: 'New message received.' }, ]); const removeNotification = (id) => { setNotifications(notifications.filter((n) => n.id !== id)); }; return ( <div style={{ padding: 20 }}> <AnimatePresence> {notifications.map((notification) => ( <motion.div key={notification.id} initial={{ opacity: 0, x: 100 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -100 }} transition={{ duration: 0.3 }} style={{ padding: 16, marginBottom: 12, background: '#e7f5ff', borderRadius: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > <p>{notification.message}</p> <button onClick={() => removeNotification(notification.id)}> × </button> </motion.div> ))} </AnimatePresence> </div> ); }
Warning: Always use unique keys when animating lists with AnimatePresence. Without proper keys, exit animations may not work correctly.

Variants for Complex Animations

Use variants to create reusable animation configurations:

import { motion } from 'framer-motion'; const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { delayChildren: 0.3, staggerChildren: 0.2, }, }, }; const itemVariants = { hidden: { y: 20, opacity: 0 }, visible: { y: 0, opacity: 1, }, }; function StaggeredList() { const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']; return ( <motion.ul variants={containerVariants} initial="hidden" animate="visible" style={{ listStyle: 'none', padding: 0 }} > {items.map((item, index) => ( <motion.li key={index} variants={itemVariants} style={{ padding: 20, marginBottom: 12, background: '#f8f9fa', borderRadius: 8, }} > {item} </motion.li> ))} </motion.ul> ); }

Page Transitions

Create smooth transitions between routes:

import { motion, AnimatePresence } from 'framer-motion'; import { Routes, Route, useLocation } from 'react-router-dom'; 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, }; function Home() { return ( <motion.div initial="initial" animate="in" exit="out" variants={pageVariants} transition={pageTransition} > <h1>Home Page</h1> <p>Welcome to the home page!</p> </motion.div> ); } function About() { return ( <motion.div initial="initial" animate="in" exit="out" variants={pageVariants} transition={pageTransition} > <h1>About Page</h1> <p>Learn more about us!</p> </motion.div> ); } function AnimatedRoutes() { const location = useLocation(); return ( <AnimatePresence mode="wait"> <Routes location={location} key={location.pathname}> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </AnimatePresence> ); }
Tip: Use mode="wait" in AnimatePresence to ensure the exit animation completes before the next component enters.

Drag and Drop Animations

Make elements draggable with built-in physics:

import { motion } from 'framer-motion'; function DraggableBox() { return ( <motion.div drag dragConstraints={{ top: -50, left: -50, right: 50, bottom: 50, }} dragElastic={0.2} whileDrag={{ scale: 1.1, cursor: 'grabbing' }} style={{ width: 150, height: 150, background: '#6c757d', borderRadius: 8, cursor: 'grab', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', }} > Drag me! </motion.div> ); } // Drag with snap back function SnapBackBox() { return ( <motion.div drag dragSnapToOrigin whileDrag={{ scale: 1.1 }} style={{ width: 150, height: 150, background: '#17a2b8', borderRadius: 8, cursor: 'grab', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', }} > I snap back! </motion.div> ); }

Layout Animations

Automatically animate layout changes with the layout prop:

import { useState } from 'react'; import { motion } from 'framer-motion'; function LayoutAnimation() { const [isExpanded, setIsExpanded] = useState(false); return ( <motion.div layout onClick={() => setIsExpanded(!isExpanded)} style={{ background: '#20c997', borderRadius: 8, padding: 20, cursor: 'pointer', width: isExpanded ? 400 : 200, height: isExpanded ? 300 : 100, }} > <motion.h3 layout>Expandable Box</motion.h3> {isExpanded && ( <motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.2 }} > This content appears when expanded! </motion.p> )} </motion.div> ); } // Grid layout animation function AnimatedGrid() { const [items, setItems] = useState([1, 2, 3, 4, 5, 6]); const shuffle = () => { setItems([...items].sort(() => Math.random() - 0.5)); }; return ( <div> <button onClick={shuffle}>Shuffle</button> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16, marginTop: 20, }} > {items.map((item) => ( <motion.div key={item} layout transition={{ type: 'spring', stiffness: 300, damping: 30 }} style={{ padding: 40, background: '#fd7e14', borderRadius: 8, color: 'white', textAlign: 'center', }} > {item} </motion.div> ))} </div> </div> ); }

Custom Animation Hooks

Create reusable animation hooks:

import { useAnimation } from 'framer-motion'; import { useEffect } from 'react'; function useScrollAnimation() { const controls = useAnimation(); useEffect(() => { const handleScroll = () => { const scrollY = window.scrollY; if (scrollY > 100) { controls.start({ opacity: 1, y: 0 }); } else { controls.start({ opacity: 0, y: 50 }); } }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [controls]); return controls; } // Using the custom hook function ScrollReveal() { const controls = useScrollAnimation(); return ( <motion.div animate={controls} initial={{ opacity: 0, y: 50 }} style={{ padding: 40, background: '#e83e8c', borderRadius: 8, color: 'white', marginTop: 200, }} > Scroll down to reveal me! </motion.div> ); }

Loading and Skeleton Animations

Create smooth loading states:

import { motion } from 'framer-motion'; function SkeletonLoader() { return ( <div> {[1, 2, 3].map((item) => ( <motion.div key={item} animate={{ opacity: [0.5, 1, 0.5], }} transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut', }} style={{ height: 100, background: '#e9ecef', borderRadius: 8, marginBottom: 12, }} /> ))} </div> ); } // Spinner animation function Spinner() { return ( <motion.div animate={{ rotate: 360 }} transition={{ duration: 1, repeat: Infinity, ease: 'linear', }} style={{ width: 50, height: 50, border: '4px solid #e9ecef', borderTopColor: '#007bff', borderRadius: '50%', }} /> ); }
Exercise 1: Create a modal component with entrance and exit animations using Framer Motion. The modal should slide in from the bottom when opened and fade out when closed. Add a backdrop with fade animation.
Exercise 2: Build an image gallery with hover effects, click-to-expand animations, and smooth transitions between grid and fullscreen views. Use AnimatePresence for the fullscreen overlay.
Exercise 3: Implement a multi-step form wizard with page transition animations. Each step should slide in from the right when moving forward and from the left when going back. Add progress indicator animations.