Framer Motion: Animations That Don't Hurt Performance
CSS animations get you 80% of the way. Framer Motion handles the hard 20%: gesture animations, layout transitions, shared element transitions, and complex sequences.
Install
npm install framer-motion
Basic Animation
import { motion } from 'framer-motion';
// Animate on mount
function Card() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
className='bg-white rounded-lg p-6'
>
Content
</motion.div>
);
}
Hover & Tap
function Button({ children }) {
return (
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
{children}
</motion.button>
);
}
Scroll-Triggered Animations
function Feature({ title, description }) {
return (
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-50px' }}
transition={{ duration: 0.5 }}
>
<h3>{title}</h3>
<p>{description}</p>
</motion.div>
);
}
Staggered Children
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.1 }
}
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 }
};
function ProductList({ products }) {
return (
<motion.ul variants={container} initial='hidden' animate='show'>
{products.map(p => (
<motion.li key={p.id} variants={item}>
<ProductCard product={p} />
</motion.li>
))}
</motion.ul>
);
}
AnimatePresence (Mount/Unmount)
import { AnimatePresence, motion } from 'framer-motion';
function Modal({ isOpen, onClose, children }) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className='fixed inset-0 z-50'
>
{children}
</motion.div>
)}
</AnimatePresence>
);
}
Layout Animations
// Animate layout changes automatically
function ExpandableCard() {
const [expanded, setExpanded] = useState(false);
return (
<motion.div layout onClick={() => setExpanded(!expanded)}
className='bg-white rounded-lg p-4 cursor-pointer'
>
<motion.h3 layout='position'>Card Title</motion.h3>
{expanded && (
<motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
Expanded content here...
</motion.p>
)}
</motion.div>
);
}
Performance Tips
- Animate
transformandopacityonly — GPU-accelerated - Avoid animating
width,height,top,left— triggers layout reflow - Use
will-change: transformfor elements that animate frequently -
viewport={{ once: true }}— don't re-animate on scroll back
Framer Motion animations ship throughout the landing page and dashboard in the AI SaaS Starter Kit. $99 at whoffagents.com.
Top comments (0)