Why Animations Matter
Well-crafted animations don't just look nice — they guide attention, communicate state changes, and make an interface feel alive. The key is subtlety: animations should enhance, never distract.
Viewport-Triggered Animations
The whileInView prop is perfect for scroll-triggered entrance effects:
function AnimatedSection({ children }) {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
{children}
</motion.div>
);
}
Stagger Children
For lists of items, stagger them so they animate in one-by-one:
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 SkillsList({ skills }) {
return (
<motion.ul variants={container} initial="hidden" animate="show">
{skills.map(skill => (
<motion.li key={skill.id} variants={item}>
{skill.name}
</motion.li>
))}
</motion.ul>
);
}
Hover Micro-interactions
<motion.div
whileHover={{ scale: 1.02, y: -4 }}
whileTap={{ scale: 0.98 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
<Card>...</Card>
</motion.div>
Performance Tips
- Use
will-change: transformvia Tailwind'swill-change-transformfor GPU-accelerated animations - Prefer animating
transformandopacity— they don't cause layout recalculation - Use
viewport={{ once: true }}so animations only run once - Avoid animating too many elements simultaneously on mobile
Result
These patterns are exactly what I use in this portfolio. The result is a smooth, performant experience that feels polished without being overdone.