Framer Motion Guide
Complete guide to using Framer Motion for smooth, pixel-perfect animations in our Next.js project
Framer Motion Guide
Complete guide to using Framer Motion for smooth, production-grade animations in our Next.js project.
What is Framer Motion?
Framer Motion is a production-ready motion library for React. It provides:
- Declarative animations - Easy to read and maintain
- Spring physics - Natural, organic movement
- Gesture support - Drag, tap, hover, etc.
- Layout animations - Automatic smooth transitions
- Server-side rendering - Works with Next.js
- TypeScript support - Full type safety
Installation
Framer Motion is already installed in this project:
"dependencies": {
"framer-motion": "^12.23.22"
}Core Concepts
1. Motion Components
Replace any HTML element with
motion.[element] to make it animatable:import { motion } from 'framer-motion';
// Before
<div className="box">Hello</div>
// After
<motion.div className="box">Hello</motion.div>2. Animation Props
Basic Animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Fades in and slides up
</motion.div>Hover Animations
<motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }}>
Click me
</motion.button>Exit Animations
import { AnimatePresence } from "framer-motion";
<AnimatePresence>
{isVisible && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
Content
</motion.div>
)}
</AnimatePresence>;Advanced Techniques
Spring Physics
The secret to natural, smooth animations:
import { motion, useSpring } from "framer-motion";
const x = useMotionValue(0);
const smoothX = useSpring(x, {
stiffness: 300, // How responsive (higher = faster)
damping: 30, // How smooth (higher = less bounce)
mass: 0.5, // Weight (lower = lighter feel)
});
<motion.div style={{ x: smoothX }}>Smooth movement</motion.div>;Spring Configuration Guide:
- Stiffness (100-1000): How quickly it responds
- Low (100-200): Slow, gentle
- Medium (300-500): Balanced
- High (600-1000): Snappy, responsive
- Damping (10-100): How much it bounces
- Low (10-20): Bouncy, elastic
- Medium (30-50): Smooth, controlled
- High (60-100): Overdamped, no bounce
- Mass (0.1-2): How heavy it feels
- Low (0.1-0.5): Light, quick
- Medium (0.5-1): Natural
- High (1-2): Heavy, inertial
Motion Values
Direct access to animation values without re-renders:
import { motion, useMotionValue, useTransform } from "framer-motion";
const x = useMotionValue(0);
const opacity = useTransform(x, [-100, 0, 100], [0, 1, 0]);
<motion.div style={{ x, opacity }}>Scroll-linked opacity</motion.div>;Scroll-Based Animations
import { motion, useScroll, useTransform } from "framer-motion";
const { scrollYProgress } = useScroll();
const scale = useTransform(scrollYProgress, [0, 1], [0.8, 1.2]);
<motion.div style={{ scale }}>Scales with scroll</motion.div>;Real-World Example: Horizontal Scroll
Our client showcase uses Framer Motion for pixel-perfect horizontal scrolling:
import { motion, useMotionValue, useSpring } from "framer-motion";
export function ClientShowcase() {
// Motion value for position
const x = useMotionValue(0);
// Spring physics for smooth movement
const smoothX = useSpring(x, {
stiffness: 300,
damping: 30,
mass: 0.5,
});
// Handle scroll events
const handleWheel = (e: WheelEvent) => {
e.preventDefault();
// Update position with micro-movements
const progress = (accumulatedDelta / threshold) * 100;
x.set(-currentSlide * 100 - progress);
};
return (
<motion.div className="flex h-full" style={{ x: smoothX }}>
{slides.map((slide) => (
<div key={slide.id}>{slide.content}</div>
))}
</motion.div>
);
}Key Features:
- Pixel-perfect tracking - Every scroll pixel updates position
- Spring physics - Smooth deceleration
- No layout shift - Pure transform animations
- GPU accelerated - Hardware optimized
Best Practices
1. Use transform Properties
❌ Avoid (causes layout recalculation):
animate={{ width: 200, height: 200 }}✅ Prefer (GPU accelerated):
animate={{ scale: 1.5 }}GPU-accelerated properties:
x,y,zscale,scaleX,scaleYrotate,rotateX,rotateY,rotateZopacity
2. Use Motion Values for Frequent Updates
❌ Causes re-renders:
const [x, setX] = useState(0);
<motion.div style={{ x }} />;✅ No re-renders:
const x = useMotionValue(0);
<motion.div style={{ x }} />;3. Optimize with will-change
For frequently animated elements:
.animated-element {
will-change: transform;
}4. Use AnimatePresence for Lists
<AnimatePresence mode="popLayout">
{items.map((item) => (
<motion.div
key={item.id}
layout
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{item.content}
</motion.div>
))}
</AnimatePresence>Common Patterns
Page Transitions
// app/layout.tsx
import { AnimatePresence } from "framer-motion";
export default function Layout({ children }) {
return <AnimatePresence mode="wait">{children}</AnimatePresence>;
}
// app/page.tsx
import { motion } from "framer-motion";
export default function Page() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
Page content
</motion.div>
);
}Stagger Animations
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map((item) => (
<motion.li key={item.id} variants={item}>
{item.text}
</motion.li>
))}
</motion.ul>;Parallax Scrolling
import { motion, useScroll, useTransform } from "framer-motion";
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 500], [0, 150]);
<motion.div style={{ y }}>Parallax element</motion.div>;Smooth Hover Cards
<motion.div
className="card"
whileHover={{
y: -8,
boxShadow: "0 20px 40px rgba(0,0,0,0.2)",
}}
transition={{
type: "spring",
stiffness: 400,
damping: 25,
}}
>
Card content
</motion.div>Performance Tips
1. Use layout Prop Sparingly
Layout animations are powerful but expensive. Use only when needed:
// Good for position changes
<motion.div layout>
Content
</motion.div>
// Better for most cases
<motion.div
animate={{ x: 100, y: 100 }}
>
Content
</motion.div>2. Reduce Motion for Accessibility
Respect user preferences:
import { useReducedMotion } from "framer-motion";
const shouldReduceMotion = useReducedMotion();
<motion.div
animate={{
scale: shouldReduceMotion ? 1 : 1.2,
transition: { duration: shouldReduceMotion ? 0 : 0.3 },
}}
>
Content
</motion.div>;3. Lazy Load Heavy Animations
import { lazy, Suspense } from "react";
const HeavyAnimation = lazy(() => import("./HeavyAnimation"));
<Suspense fallback={<div>Loading...</div>}>
<HeavyAnimation />
</Suspense>;Debugging
Visualize Animation Values
import { motion, useMotionValue } from "framer-motion";
import { useEffect } from "react";
const x = useMotionValue(0);
useEffect(() => {
const unsubscribe = x.on("change", (latest) => {
console.log("x:", latest);
});
return unsubscribe;
}, [x]);Check for Performance Issues
// Add this to see when components re-render
useEffect(() => {
console.log("Component rendered");
});Resources
Official Documentation
Examples in This Project
src/components/layout/sections/landing/client-showcase.tsx- Horizontal scroll with spring physics- More examples coming soon!
Community Resources
- Framer Motion Examples
- Motion One - Lightweight alternative for simple animations
Troubleshooting
Animation Not Working
-
Check if component is wrapped in motion:
// ❌ Won't animate <div animate={{ x: 100 }} /> // ✅ Will animate <motion.div animate={{ x: 100 }} /> -
Verify initial values:
// ❌ No visible change <motion.div animate={{ opacity: 1 }} /> // ✅ Visible change <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
Janky Animations
- Use transform properties (x, y, scale) instead of layout properties (width, height, top, left)
- Add
will-change: transformCSS - Reduce complexity - fewer animated elements perform better
- Check for forced reflows - avoid reading layout properties during animation
Exit Animations Not Playing
Always wrap with
AnimatePresence:import { AnimatePresence } from "framer-motion";
<AnimatePresence>{show && <motion.div exit={{ opacity: 0 }}>Content</motion.div>}</AnimatePresence>;Next Steps
- Experiment with spring physics in
client-showcase.tsx - Try adding hover animations to cards
- Implement page transitions
- Create custom animation variants
For questions or help, check the official documentation or ask the team!