React has revolutionized the way we build user interfaces, but when it comes to adding smooth animations, things can get a bit tricky. Enter Framer Motion – a game-changer in the world of React animations. This powerful library makes it super easy to create stunning, complex animations that’ll have your users saying “wow!”
Let’s dive into the world of Framer Motion and see how it can take your React apps to the next level. First things first, you’ll need to install the library. Just pop open your terminal and run:
npm install framer-motion
Once you’ve got that sorted, you’re ready to start animating! The basic building block of Framer Motion is the motion
component. It’s like a supercharged version of your regular HTML elements. Want to animate a div? Use motion.div
. A button? motion.button
. You get the idea.
Here’s a simple example to get you started:
import { motion } from 'framer-motion';
function AnimatedBox() {
return (
<motion.div
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
>
Hello, I'm animated!
</motion.div>
);
}
In this snippet, we’re creating a div that fades in and scales up when it first appears. The initial
prop sets the starting state, animate
defines where we want to end up, and transition
controls how we get there.
But that’s just scratching the surface. Framer Motion really shines when you start building more complex animations. Let’s say you want to create a card that flips over when you hover on it. Here’s how you might do that:
import { motion } from 'framer-motion';
import { useState } from 'react';
function FlipCard() {
const [isFlipped, setIsFlipped] = useState(false);
return (
<motion.div
style={{ width: 200, height: 300, background: 'lightblue' }}
animate={{ rotateY: isFlipped ? 180 : 0 }}
transition={{ duration: 0.6 }}
onHoverStart={() => setIsFlipped(true)}
onHoverEnd={() => setIsFlipped(false)}
>
{isFlipped ? 'Back of card' : 'Front of card'}
</motion.div>
);
}
Pretty cool, right? We’re using the useState
hook to keep track of whether the card is flipped or not, and then we’re animating the rotateY
property based on that state. The onHoverStart
and onHoverEnd
props make it easy to trigger the animation when the user interacts with the card.
Now, let’s talk about gesture-based interactions. Framer Motion has built-in support for drag, tap, hover, and pan gestures. Here’s an example of a draggable element:
import { motion } from 'framer-motion';
function DraggableBox() {
return (
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
style={{ width: 100, height: 100, background: 'coral' }}
>
Drag me!
</motion.div>
);
}
The drag
prop enables dragging, and dragConstraints
limits how far the element can be dragged. It’s that simple!
But what if you want to create more complex, multi-step animations? Framer Motion has got you covered with its variants
feature. Variants let you define named animation states that you can easily switch between. Here’s an example:
import { motion } from 'framer-motion';
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
function AnimatedList() {
return (
<motion.ul
initial="hidden"
animate="visible"
variants={variants}
>
{['Apple', 'Banana', 'Cherry'].map((fruit, index) => (
<motion.li
key={fruit}
variants={variants}
transition={{ delay: index * 0.3 }}
>
{fruit}
</motion.li>
))}
</motion.ul>
);
}
In this example, we’re defining hidden
and visible
variants for both the list and its items. The cool thing is, when we animate the parent ul
to the visible
state, all the child li
elements automatically inherit that animation. We’re also adding a staggered delay to each item for a nice cascading effect.
One of the things I love most about Framer Motion is how it handles layout animations. Let’s say you have a list of items, and you want to animate them smoothly when an item is added or removed. Framer Motion makes this a breeze with its AnimatePresence
component:
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
function AnimatedList() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const addItem = () => {
setItems([...items, `Item ${items.length + 1}`]);
};
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};
return (
<div>
<button onClick={addItem}>Add Item</button>
<ul>
<AnimatePresence>
{items.map((item, index) => (
<motion.li
key={item}
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3 }}
>
{item}
<button onClick={() => removeItem(index)}>Remove</button>
</motion.li>
))}
</AnimatePresence>
</ul>
</div>
);
}
The AnimatePresence
component allows us to animate elements as they’re removed from the React tree. When an item is added or removed, Framer Motion takes care of animating the height and opacity smoothly.
Now, let’s talk about something really cool – animating SVGs with Framer Motion. SVGs are perfect for creating complex, scalable animations, and Framer Motion makes it super easy to bring them to life. Here’s a simple example of an animated loading spinner:
import { motion } from 'framer-motion';
function LoadingSpinner() {
return (
<motion.svg
width="50"
height="50"
viewBox="0 0 50 50"
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
>
<motion.circle
cx="25"
cy="25"
r="20"
stroke="#333"
strokeWidth="4"
fill="none"
animate={{ strokeDasharray: ['1, 150', '90, 150', '90, 150'] }}
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
/>
</motion.svg>
);
}
This creates a spinning circle that also has a “drawing” animation. Pretty nifty, right?
But what if you want to create really complex, custom animations? Framer Motion has you covered with its powerful useAnimation
hook. This lets you create and control animations programmatically. Here’s an example of a button that does a little dance when you click it:
import { motion, useAnimation } from 'framer-motion';
function DancingButton() {
const controls = useAnimation();
const handleClick = async () => {
await controls.start({ scale: 1.2 });
await controls.start({ rotate: [0, 10, -10, 10, -10, 0] });
await controls.start({ scale: 1 });
};
return (
<motion.button
animate={controls}
onClick={handleClick}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
Click me!
</motion.button>
);
}
When you click the button, it scales up, does a little wiggle, and then scales back down. The useAnimation
hook gives you fine-grained control over your animations, letting you chain them together and trigger them based on any events or conditions you want.
One thing I’ve found super useful in my projects is Framer Motion’s support for spring animations. Springs add a natural, physics-based feel to your animations. Instead of rigid, linear movements, you get smooth, slightly bouncy transitions that just feel right. Here’s how you might use a spring animation:
import { motion } from 'framer-motion';
function SpringyBox() {
return (
<motion.div
style={{ width: 100, height: 100, background: 'lightgreen' }}
animate={{ x: 100 }}
transition={{ type: 'spring', stiffness: 100, damping: 10 }}
>
Boing!
</motion.div>
);
}
This creates a box that springs to the right when it first appears. You can tweak the stiffness
and damping
values to get exactly the feel you want.
Now, let’s talk about something that’s often overlooked but can really make your animations pop – orchestration. Framer Motion makes it easy to coordinate multiple animations to create complex, eye-catching sequences. Here’s an example of a loading animation that coordinates several elements:
import { motion } from 'framer-motion';
const containerVariants = {
start: { transition: { staggerChildren: 0.2 } },
end: { transition: { staggerChildren: 0.2 } },
};
const circleVariants = {
start: { y: '0%' },
end: { y: '100%' },
};
const transition = { duration: 0.5, yoyo: Infinity, ease: 'easeInOut' };
function LoadingDots() {
return (
<motion.div
style={{ display: 'flex', gap: 10 }}
variants={containerVariants}
initial="start"
animate="end"
>
{[1, 2, 3].map((index) => (
<motion.div
key={index}
style={{ width: 20, height: 20, background: 'blue', borderRadius: '50%' }}
variants={circleVariants}
transition={transition}
/>
))}
</motion.div>
);
}
This creates a row of three dots that bounce up and down in sequence. The staggerChildren
property in the container variants creates the delay between each dot’s animation.
One of the coolest things about Framer Motion is how it handles interruptions. In the real world, users don’t always wait for one animation to finish before triggering another. Framer Motion handles these interruptions gracefully, ensuring smooth transitions even when animations are cut short. This is all handled automatically, but you can also control it manually if you need to.
Now, let’s talk about performance. When you’re building complex animations, especially on lower-powered devices, performance can become an issue. Framer Motion is built with performance in mind, but there are still some things you can do to optimize your animations. One key technique is to use the useDragControls
hook for draggable elements. This allows you to start the drag operation on a separate element, which can help prevent jank on mobile devices:
import { motion, useDragControls } from 'framer-motion';
function OptimizedDraggable() {
const dragControls = useDragControls();
return (
<div>
<div onPointerDown={(e) => dragControls.