Advanced React Animations: Mastering Framer Motion & GSAP for Stunning UIs


Introduction
In today's competitive digital landscape, a captivating user experience is paramount. Smooth, intuitive, and engaging animations are no longer just a "nice-to-have" but a fundamental expectation. While CSS transitions and basic React state manipulations can handle simple effects, building truly advanced, performant, and delightful web animations often requires more specialized tools.
This comprehensive guide delves into two of the most powerful and popular animation libraries in the React ecosystem: Framer Motion and GSAP (GreenSock Animation Platform). We'll explore their individual strengths, learn how to leverage them effectively in React, and crucially, discover strategies for combining them to create animations that are both robust and breathtaking. Whether you're animating complex SVG paths, orchestrating intricate page transitions, or building sophisticated scroll-triggered effects, this article will equip you with the knowledge and examples to master advanced web animations.
Prerequisites
To get the most out of this guide, you should have:
- A solid understanding of React fundamentals (components, props, state, hooks).
- Familiarity with functional components and the
useState,useEffect, anduseRefhooks. - Basic knowledge of CSS and JavaScript.
- Node.js and npm/yarn installed on your machine.
1. The Animation Landscape in React: Choosing Your Tools
React offers several approaches to animation, each with its own advantages:
- CSS Transitions/Animations: Simple, performant for basic effects, but can become cumbersome for complex orchestrations or dynamic values.
- React Spring: A physics-based animation library, great for natural, fluid interactions.
- Framer Motion: A declarative, React-specific animation library known for its ease of use, component-based approach, and excellent developer experience.
- GSAP: A powerful, high-performance, and feature-rich JavaScript animation library, framework-agnostic, offering unparalleled control and precision.
For advanced scenarios, Framer Motion and GSAP stand out. Framer Motion excels at declarative, component-driven UI animations, handling gestures, layout animations, and presence/exit animations with elegance. GSAP, on the other hand, is the go-to for pixel-perfect control, complex timelines, animating arbitrary properties, and scroll-triggered effects, often outperforming other libraries in raw performance.
2. Getting Started with Framer Motion: Declarative Power
Framer Motion provides a simple, declarative API to animate React components. It extends standard HTML and SVG elements into motion components, allowing you to define animation properties directly as props.
Installation
npm install framer-motion
# or
yarn add framer-motionBasic Animations
The core of Framer Motion is the motion component. You define initial and animate states, and Framer Motion handles the interpolation.
import React from 'react';
import { motion } from 'framer-motion';
function BasicAnimation() {
return (
<motion.div
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
style={{
width: '100px',
height: '100px',
backgroundColor: 'blue',
borderRadius: '10px'
}}
>
Hello Framer Motion
</motion.div>
);
}
export default BasicAnimation;Gestures and Interaction
Framer Motion makes adding interactive gestures incredibly easy.
import React from 'react';
import { motion } from 'framer-motion';
function InteractiveBox() {
return (
<motion.div
whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.9, borderRadius: "50%" }}
drag="x"
dragConstraints={{ left: -100, right: 100 }}
style={{
width: '150px',
height: '150px',
backgroundColor: 'purple',
cursor: 'grab',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
fontWeight: 'bold'
}}
>
Drag Me
</motion.div>
);
}
export default InteractiveBox;3. Getting Started with GSAP: Imperative Precision
GSAP is a robust, high-performance animation engine. Unlike Framer Motion's declarative React components, GSAP operates imperatively, allowing you to precisely control every aspect of an animation.
Installation
npm install gsap
# or
yarn add gsapBasic Tweens
GSAP's core functionality revolves around "tweens" – animating properties of an object over time. You typically target DOM elements using useRef in React.
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
function GSAPBasicAnimation() {
const boxRef = useRef(null);
useEffect(() => {
gsap.to(boxRef.current, {
x: 200,
rotation: 360,
duration: 1.5,
ease: "power2.out",
delay: 0.5
});
}, []); // Run animation once on mount
return (
<div
ref={boxRef}
style={{
width: '120px',
height: '120px',
backgroundColor: 'teal',
borderRadius: '10px'
}}
>
GSAP Box
</div>
);
}
export default GSAPBasicAnimation;Timelines for Orchestration
gsap.timeline() is essential for sequencing multiple animations, creating complex choreographies.
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
function GSAPTimelineAnimation() {
const box1Ref = useRef(null);
const box2Ref = useRef(null);
useEffect(() => {
const tl = gsap.timeline({
defaults: { ease: "power1.out" },
repeat: -1, // Repeat indefinitely
yoyo: true // Go back and forth
});
tl.to(box1Ref.current, { x: 150, duration: 1 })
.to(box2Ref.current, { y: 100, rotation: 180, duration: 0.8 }, "<0.2") // Start 0.2s before box1 finishes
.to(box1Ref.current, { backgroundColor: "red", duration: 0.5 }, "+0.5"); // Start 0.5s after previous finishes
return () => tl.kill(); // Clean up timeline on unmount
}, []);
return (
<>
<div
ref={box1Ref}
style={{
width: '80px',
height: '80px',
backgroundColor: 'orange',
margin: '10px'
}}
></div>
<div
ref={box2Ref}
style={{
width: '80px',
height: '80px',
backgroundColor: 'green',
margin: '10px'
}}
></div>
</>
);
}
export default GSAPTimelineAnimation;4. Framer Motion for Declarative UI Animations: Advanced Features
Framer Motion shines in building interactive, state-driven UI animations.
Variants for State Management and Orchestration
variants allow you to define animation states and orchestrate animations between child components.
import React, { useState } from 'react';
import { motion } from 'framer-motion';
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1, // Stagger children by 0.1 seconds
delayChildren: 0.2 // Delay children animation start
}
},
exit: { opacity: 0, transition: { duration: 0.3 } }
};
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: { y: 0, opacity: 1 },
exit: { opacity: 0, y: -20 }
};
function ListAnimation() {
const [showList, setShowList] = useState(false);
return (
<div>
<button onClick={() => setShowList(!showList)}>
Toggle List
</button>
{showList && (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
exit="exit"
style={{ listStyle: 'none', padding: 0 }}
>
{[1, 2, 3].map(item => (
<motion.li key={item} variants={itemVariants}
style={{
padding: '10px',
margin: '5px 0',
backgroundColor: '#eee',
borderRadius: '5px'
}}
>
Item {item}
</motion.li>
))}
</motion.ul>
)}
</div>
);
}
export default ListAnimation;Exit Animations with AnimatePresence
AnimatePresence allows components to animate out when they are removed from the React tree.
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
function ExitAnimation() {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
Toggle Box
</button>
<AnimatePresence mode="wait"> {/* mode="wait" ensures exit completes before entry */}
{isVisible && (
<motion.div
key="myBox"
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.3 } }}
transition={{ duration: 0.5, ease: "easeOut" }}
style={{
width: '100px',
height: '100px',
backgroundColor: 'red',
marginTop: '20px',
borderRadius: '10px'
}}
>
Fade & Scale
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default ExitAnimation;Layout Animations (layout prop)
Framer Motion's layout prop automatically animates changes in size or position, making responsive animations effortless.
import React, { useState } from 'react';
import { motion } from 'framer-motion';
function LayoutAnimation() {
const [isOn, setIsOn] = useState(false);
const toggleSwitch = () => setIsOn(!isOn);
return (
<div
style={{
width: '100px',
height: '50px',
backgroundColor: isOn ? '#4CAF50' : '#ccc',
borderRadius: '25px',
display: 'flex',
justifyContent: isOn ? 'flex-end' : 'flex-start',
alignItems: 'center',
padding: '5px',
cursor: 'pointer'
}}
onClick={toggleSwitch}
>
<motion.div
layout // This enables automatic layout animation
transition={{ type: "spring", stiffness: 700, damping: 30 }}
style={{
width: '40px',
height: '40px',
backgroundColor: 'white',
borderRadius: '50%',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
}}
/>
</div>
);
}
export default LayoutAnimation;5. GSAP for High-Performance, Complex Sequences: Beyond the Basics
GSAP excels where pixel-perfect control, complex property animation, and advanced sequencing are required.
Animating SVG Paths and Custom Properties
GSAP can animate virtually any numeric property, including SVG attributes or even custom JavaScript object properties.
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
function SVGPathAnimation() {
const pathRef = useRef(null);
useEffect(() => {
gsap.to(pathRef.current, {
duration: 2,
attr: { d: "M10 80 Q 77.5 10 145 80 T 280 80" }, // Animate SVG 'd' attribute
ease: "power1.inOut",
repeat: -1,
yoyo: true
});
}, []);
return (
<svg width="300" height="100" viewBox="0 0 300 100">
<path
ref={pathRef}
d="M10 80 Q 77.5 80 145 80 T 280 80"
stroke="blue"
strokeWidth="3"
fill="transparent"
/>
</svg>
);
}
export default SVGPathAnimation;Scroll-Triggered Animations with ScrollTrigger
ScrollTrigger is a powerful GSAP plugin for creating scroll-based animations, parallax effects, and pinning elements.
First, install the plugin:
npm install gsap @gsap/scrolltriggerThen, use it in your component:
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
function ScrollAnimation() {
const sectionRef = useRef(null);
const boxRef = useRef(null);
useEffect(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionRef.current,
start: "top center", // When the top of the trigger hits the center of the viewport
end: "bottom top", // When the bottom of the trigger hits the top of the viewport
scrub: true, // Link animation progress to scroll position
markers: false, // Set to true for debugging scroll positions
// pin: true, // Uncomment to pin the section during animation
}
});
tl.to(boxRef.current, { x: 500, rotation: 360, scale: 1.5, ease: "none" })
.to(boxRef.current, { backgroundColor: "pink", duration: 0.1 }, "<0.2"); // Change color slightly before end
return () => tl.kill(); // Clean up on unmount
}, []);
return (
<div>
<div style={{ height: '100vh', display: 'grid', placeItems: 'center' }}>
<h1>Scroll Down!</h1>
</div>
<div
ref={sectionRef}
style={{
height: '100vh',
backgroundColor: '#f0f0f0',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<div
ref={boxRef}
style={{
width: '100px',
height: '100px',
backgroundColor: 'orange',
borderRadius: '10px'
}}
>
Scroll Box
</div>
</div>
<div style={{ height: '100vh', display: 'grid', placeItems: 'center' }}>
<h1>End of Scroll Section</h1>
</div>
</div>
);
}
export default ScrollAnimation;6. Combining Framer Motion and GSAP: When and How
The true power lies in understanding when to use each library and how to combine them strategically.
- Use Framer Motion for: Declarative component entry/exit, state-driven UI changes, interactive gestures (hover, tap, drag), layout animations, and simple, common motion effects.
- Use GSAP for: Pixel-perfect control over complex sequences, animating non-CSS properties (SVG paths, WebGL uniforms), scroll-triggered effects (
ScrollTrigger), high-performance, complex timelines, and when you need to integrate with external JavaScript libraries that require imperative control.
How to Combine Them
The most common pattern is to let Framer Motion handle the component's lifecycle (mount/unmount, gestures) and then use GSAP for specific, complex animations within that component, often triggered by Framer Motion's lifecycle events or state changes.
You can trigger GSAP animations from within Framer Motion components using useRef and useEffect, or by calling GSAP tweens/timelines in Framer Motion's onAnimationComplete or onMount callbacks.
7. Practical Use Case 1: Interactive Card Flip (Framer Motion)
Let's create a 3D card flip animation using Framer Motion's useCycle hook for state management and motion components for the animation.
import React, { useState } from 'react';
import { motion, useCycle } from 'framer-motion';
const cardVariants = {
front: { rotateY: 0 },
back: { rotateY: 180 }
};
function FlipCard() {
const [isFlipped, toggleFlip] = useCycle(false, true);
return (
<div
style={{
perspective: '1000px',
width: '200px',
height: '300px',
cursor: 'pointer',
margin: '50px'
}}
onClick={() => toggleFlip()}
>
<motion.div
variants={cardVariants}
initial="front"
animate={isFlipped ? "back" : "front"}
transition={{ duration: 0.6, ease: "easeInOut" }}
style={{
width: '100%',
height: '100%',
position: 'relative',
transformStyle: 'preserve-3d'
}}
>
{/* Front of the card */}
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#4CAF50',
backfaceVisibility: 'hidden',
borderRadius: '10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
fontSize: '1.5em',
fontWeight: 'bold'
}}
>
Front
</div>
{/* Back of the card */}
<div
style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#2196F3',
backfaceVisibility: 'hidden',
transform: 'rotateY(180deg)', // Initially rotated to show back
borderRadius: '10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
fontSize: '1.5em',
fontWeight: 'bold'
}}
>
Back
</div>
</motion.div>
</div>
);
}
export default FlipCard;8. Practical Use Case 2: Complex Page Transition (Framer Motion + GSAP)
This example demonstrates a sophisticated page transition where Framer Motion handles the overall component presence, and GSAP orchestrates staggered animations of elements within the entering component.
import React, { useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { gsap } from 'gsap';
const pageVariants = {
initial: { opacity: 0, y: 50 },
animate: { opacity: 1, y: 0, transition: { duration: 0.5 } },
exit: { opacity: 0, y: -50, transition: { duration: 0.3 } }
};
function AnimatedPageContent() {
const titleRef = useRef(null);
const textRef = useRef(null);
const buttonRef = useRef(null);
useEffect(() => {
// GSAP animation for elements *within* the page after it mounts
const tl = gsap.timeline({
delay: 0.3 // Delay GSAP start slightly after Framer Motion page entry
});
tl.from(titleRef.current, { y: -30, opacity: 0, duration: 0.6, ease: "power3.out" })
.from(textRef.current, { y: 20, opacity: 0, duration: 0.5, ease: "power2.out" }, "<0.2") // Staggered
.from(buttonRef.current, { scale: 0.8, opacity: 0, duration: 0.4, ease: "back.out(1.7)" }, "<0.2");
return () => tl.kill(); // Cleanup GSAP timeline
}, []);
return (
<motion.div
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
style={{
padding: '40px',
backgroundColor: '#fff',
minHeight: 'calc(100vh - 80px)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
boxShadow: '0 4px 15px rgba(0,0,0,0.1)'
}}
>
<h2 ref={titleRef} style={{ fontSize: '3em', color: '#333' }}>Welcome to Our Service</h2>
<p ref={textRef} style={{ fontSize: '1.2em', maxWidth: '600px', margin: '20px 0', lineHeight: '1.6', color: '#666' }}>
Discover a world of possibilities with our innovative solutions. We are dedicated to providing you with the best experience.
</p>
<button
ref={buttonRef}
style={{
padding: '15px 30px',
fontSize: '1.1em',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
marginTop: '30px',
boxShadow: '0 2px 10px rgba(0,123,255,0.3)'
}}
>
Learn More
</button>
</motion.div>
);
}
// Parent component to manage page transitions
function PageTransitionContainer() {
const [showPage, setShowPage] = useState(true);
return (
<div>
<button onClick={() => setShowPage(!showPage)} style={{ margin: '20px', padding: '10px 20px' }}>
Toggle Page Content
</button>
<AnimatePresence mode="wait">
{showPage && <AnimatedPageContent key="myAnimatedPage" />}
</AnimatePresence>
</div>
);
}
export default PageTransitionContainer;9. Practical Use Case 3: Scroll-Triggered Hero Animation (GSAP ScrollTrigger)
Let's build a hero section where elements animate based on scroll progress, including a parallax effect.
import React, { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
function HeroScrollAnimation() {
const heroRef = useRef(null);
const titleRef = useRef(null);
const subtitleRef = useRef(null);
const imageRef = useRef(null);
useEffect(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: heroRef.current,
start: "top top",
end: "bottom top",
scrub: true,
pin: true, // Pin the hero section during scroll
markers: false
}
});
// Animate title and subtitle fade out and move up
tl.to(titleRef.current, { y: -100, opacity: 0, duration: 1 }, 0)
.to(subtitleRef.current, { y: -50, opacity: 0, duration: 1 }, 0)
// Parallax effect for the image
.to(imageRef.current, { y: 200, scale: 1.2, duration: 1, ease: "none" }, 0);
return () => tl.kill();
}, []);
return (
<div>
<div
ref={heroRef}
style={{
height: '100vh',
backgroundColor: '#f0f8ff',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
overflow: 'hidden' // Hide overflow for parallax image
}}
>
<img
ref={imageRef}
src="https://via.placeholder.com/1200x800?text=Nature+Scene"
alt="Nature Scene"
style={{
position: 'absolute',
width: '100%',
height: '100%',
objectFit: 'cover',
zIndex: 0
}}
/>
<h1 ref={titleRef} style={{ fontSize: '4em', color: 'white', textShadow: '2px 2px 5px rgba(0,0,0,0.5)', zIndex: 1 }}>
Explore the Wilderness
</h1>
<p ref={subtitleRef} style={{ fontSize: '1.5em', color: 'white', textShadow: '1px 1px 3px rgba(0,0,0,0.5)', zIndex: 1 }}>
A journey into the heart of nature
</p>
</div>
<div style={{ height: '150vh', backgroundColor: '#eee', padding: '50px' }}>
<h2>Content Below Hero</h2>
<p>This is some content that appears after the hero section.</p>
<p>Keep scrolling to see more...</p>
</div>
</div>
);
}
export default HeroScrollAnimation;10. Best Practices for Performance & Maintainability
Creating stunning animations shouldn't come at the cost of performance or code clarity.
Performance Optimization
- Hardware Acceleration: Prioritize animating
transform(e.g.,x,y,scale,rotate) andopacity. These properties can be animated directly by the GPU, leading to smoother animations. Avoid animating properties likewidth,height,margin,padding, orleft/topwithoutposition: absolute, as they trigger layout recalculations. - Minimize DOM Manipulation: Batch DOM updates where possible. GSAP is highly optimized for this.
- Debounce/Throttle Event Listeners: For animations tied to events like
resizeormousemove, use debouncing or throttling to limit how often the animation logic runs. will-changeProperty: Use the CSSwill-changeproperty judiciously to hint to the browser which properties will change, allowing it to optimize rendering. Don't overuse it, as it can consume significant resources.- Accessibility: Ensure animations don't detract from usability. Provide options for users to disable or reduce animations, especially for those with vestibular disorders. Respect
prefers-reduced-motionmedia query.
Code Maintainability
- Organize Animation Logic: For complex animations, encapsulate logic within custom hooks (e.g.,
useAnimatedHero,useCardFlip) or dedicated animation utility files. - Use Variants (Framer Motion): Leverage
variantsfor managing complex animation states and orchestrating child components, making your animation code more readable and reusable. - GSAP Timelines: For sequential or overlapping animations, always use
gsap.timeline(). This simplifies control and ensures proper cleanup. - Cleanup: Crucially, always clean up GSAP timelines and ScrollTriggers in
useEffect's return function to prevent memory leaks, especially in single-page applications where components mount and unmount frequently.
useEffect(() => {
const tl = gsap.timeline();
// ... animations
return () => tl.kill(); // Important for cleanup!
}, []);11. Common Pitfalls and Troubleshooting
Even with powerful tools, you might encounter issues. Here are some common pitfalls:
- Flickering Animations: Often caused by animating non-GPU-accelerated properties, rapid re-renders, or conflicts between CSS and JavaScript animations. Ensure you're animating
transformandopacitywhere possible. Check for excessive state updates. - Memory Leaks (GSAP): Forgetting to
kill()GSAP timelines orScrollTriggerinstances when a component unmounts is a common source of memory leaks. Always use theuseEffectcleanup function. - Conflicting Styles: Ensure your CSS isn't overriding animation properties set by Framer Motion or GSAP. Use
!importantsparingly, and understand the cascade. - Performance on Mobile: Mobile devices have less processing power. Test your animations thoroughly on various devices. Reduce complexity or disable non-essential animations for smaller screens.
- Incorrect
useRefUsage: When working with GSAP, ensureuseRefis correctly attached to the DOM element you intend to animate, and that the element is available when theuseEffecthook runs. AnimatePresencekeyProp: ForAnimatePresenceto detect when a component is added or removed, it must have a uniquekeyprop that changes when the component's identity changes (or is removed).- GSAP Plugin Registration: Remember to
gsap.registerPlugin()for any GSAP plugins you use (e.g.,ScrollTrigger,Draggable).
Conclusion
Mastering advanced web animations in React with Framer Motion and GSAP unlocks a new level of user experience. Framer Motion empowers you with declarative, component-driven animations, perfect for interactive UI elements, gestures, and layout transitions. GSAP provides unparalleled imperative control, performance, and precision for complex timelines, SVG animations, and sophisticated scroll-triggered effects.
By understanding the strengths of each library and strategically combining them, you can build highly engaging, performant, and maintainable animations. Remember to prioritize performance with hardware-accelerated properties, maintain clean code with proper organization and cleanup, and always consider accessibility. Experiment with the examples, explore their extensive documentation, and push the boundaries of what's possible in web animation. Your users will thank you for the delightful experience.

Written by
CodewithYohaFull-Stack Software Engineer with 5+ years of experience in Java, Spring Boot, and cloud architecture across AWS, Azure, and GCP. Writing production-grade engineering patterns for developers who ship real software.



