Framer Motion Examples
Animate through Multiple States with Keyframes
export default function Index() {
return (
<motion.div
animate={{opacity: 1, y: [-200, 0, 200, 0, -200, 0]}}
transition={{duration: 2, times: [0, 0.2, 0.3, 0.7, 0.9, 1]}}
>
up and down bounce
</motion.div>
);
}
-
times
is an array from 0 to 1, like keyframe percentage. -
duration
tells the whole animation time. -
at time 0, div is at -200
-
at 20%, div is at 0
-
at 30%, div is at 200
Initial Prop to Define the Beginning State of Animations
export default function Index() {
return (
<motion.div
initial={{opacity: 0, y: -200}}
animate={{opacity: 1, y: 0}}
transition={{duration: 0.4, ease: 'easeOut', type: 'tween'}}
>
fade in from top
</motion.div>
);
}
initial
is the initial state of the element, and animate
is the final end state.
Use this for hero headings, description, etc
Variants, making states semantic and reusable
Let's update the above example with variants:
const fadeInVariant = {
hidden: {opacity: 0, y: -200},
visible: {opacity: 1, y: 0, transition: {duration: 0.5, ease: 'easeOut', type: 'tween'}},
};
export default function Index() {
return (
<motion.div initial="hidden" animate="visible" variants={fadeInVariant}>
fade in from top
</motion.div>
);
}
Animate Elements Removed by AnimatePresence and Exit
Let's say we have a todo list whose item will fade in when a new one is added and fade out when removed. We need to wrap
AnimatePresence
with the list and add exit
prop indicating the exit state.
const fadeInVariant = {
hidden: {opacity: 0, y: -200},
visible: {opacity: 1, y: 0, transition: {duration: 0.5, ease: 'easeOut', type: 'tween'}},
};
export default function Index() {
const items = ['item 1', 'item 2'];
return (
<ul>
<AnimatePresence>
{items.map((i) => (
<motion.li
key={i}
initial="hidden"
animate="visible"
exit="hidden"
layoutId={i}
variants={fadeInVariant}
></motion.li>
))}
</AnimatePresence>
</ul>
);
}
Animate Elements When Their Layout Changes with Framer Motion
layoutId
. Use thelayoutId
prop to automatically animate layout changes across, and between, multiple components.
I think layoutId
value could be the key
value. One element exiting will need it. If only 1 element exists for the
animation, I don't think it is needed.
Stagger
The custom
prop let's you create dynamic variant values to animate each component differently.
const fadeInVariant = {
hidden: {opacity: 0, y: -200},
+ visible: (custom) => ({opacity: 1, y: 0, transition: {duration: 0.5, ease: 'easeOut', type: 'tween', delay: custom}}),
};
export default function Index() {
const items = ['item 1', 'item 2'];
return (
<ul>
<AnimatePresence>
{items.map((i, index) => (
<motion.li
key={i}
initial="hidden"
animate="visible"
exit="hidden"
layoutId={i}
+ custom={(index + 1) * 0.2}
variants={fadeInVariant}
></motion.li>
))}
</AnimatePresence>
</ul>
);
}
Create Micro Interactions with Gesture Props
Framer Motion provides helper props like whileHover
& whileTap
, that will temporarily animate a component to a
visual state when that gesture is happening.
const fadeInVariant = {
hidden: {opacity: 0, y: -200},
visible: (custom) => ({opacity: 1, y: 0, transition: {duration: 0.5, ease: 'easeOut', type: 'tween', delay: custom}}),
};
export default function Index() {
const items = ['item 1', 'item 2'];
return (
<ul>
<AnimatePresence>
{items.map((i, index) => (
<motion.li
key={i}
initial="hidden"
animate="visible"
exit="hidden"
layoutId={i}
custom={(index + 1) * 0.2}
variants={fadeInVariant}
+ whileHover={{scale: 1.05}}
+ whileTap={{scale: 1.1}}
></motion.li>
))}
</AnimatePresence>
</ul>
);
}
SVG path animation
import {motion} from 'framer-motion';
const svgVariants = {
start: {opacity: 0, pathLength: 0},
end: {opacity: 1, pathLength: 1, transition: {duration: 2, ease: 'easeInOut'}},
};
export default function MenuButton({onClick}: {onClick: () => void}) {
return (
<button onClick={onClick}>
<motion.svg initial={false} className="group h-10 w-10" viewBox="0 0 56 56" fill="none">
<motion.circle
initial="start"
animate="end"
variants={svgVariants}
cx="28"
cy="28"
r="27"
className="stroke-gray-300 group-hover:stroke-gray-900 dark:stroke-gray-600 dark:group-hover:stroke-gray-100"
strokeWidth="2"
/>
<motion.path
initial="start"
animate="end"
variants={svgVariants}
d="M17.8182 35.6364H38.1818M17.8182 20.3636H38.1818H17.8182ZM17.8182 28H38.1818H17.8182Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</motion.svg>
</button>
);
}
Path morphing
Scroll-based animations
Create declarative, reactive chains of MotionValues that can update as a result of animations and/or gestures.
const {scrollYProgress} = useViewportScroll();
return <motion.path style={{pathLength: scrollYProgress}} />;
Server-side rendering
The animated state of a component will be rendered server-side to prevent flashes of re-styled content after your JavaScript loads.
<motion.div initial={false} animate={{x: 100}} />
MotionValues
Create declarative, reactive chains of MotionValues that can update as a result of animations and/or gestures.
MotionValues track the state and velocity of animating values.
const x = useMotionValue(0);
const xinput = [-200, 0, 200];
const opacityOutput = [0, 1, 0];
const colorOutput = ['#f00', '#fff', '#0f0'];
const opacity = useTransform(x, xInput, opacityOutput);
const color = useTransform(x, xInput, colorOutput);
return <motion.div drag="x" style={{x, opacity, color}} />;
initial x is 0, the opacity will be 1. When dragging over to -200 or 200, opacity will be 0.
Scroll-triggered animations
Motion also provides a whileInView prop that defines a visual state to animate to while a component is in the viewport.
<motion.div initial={{opacity: 0}} whileInView={{opacity: 1}} viewport={{once: true}} />
Scroll-linked animations
The useViewportScroll hook provides four read-only MotionValues, two that return the viewport's x/y scroll position in pixels, and two that return it as progress value between 0 and 1.
You can use these MotionValues to declaratively drive features like progress indicators or parallax effects.
import {motion, useViewportScroll} from 'framer-motion';
export const CircleIndicator = () => {
const {scrollYProgress} = useViewportScroll();
// a svg progress indicator
return <motion.path d="M 0, 20 a 20, 20 0 1,0 40,0 a 20, 20 0 1,0 -40,0" style={{pathLength: scrollYProgress}} />;
};
Layout animation
To automatically animate the layout of a motion component when its size or position changes, give it a layout
prop.
<motion.div layout />
Shared layout animations
When a new motion component is added, it can be automatically animated from another one by giving them both the same layoutId prop.
isSelected ? <motion.div layoutId="underline" /> : null;