Framer Motion is a popular open-source React animation library, aimed at creating production-ready animation. It possesses a low-level declarative API and can be used for the web as well as for mobile apps. Framer Motion is Pose’s animation library next-in-line, and its other advantage valued by the software developers is that it’s also possible to get it as a separate package for use in React apps.
Framer’s documentation provides enough tutorials on how to do the simplest gestures and motion, but for more sophisticated cases there’s too little information on the web. So this Framer Motion tutorial skips the basics and goes straight to more complex animations, like the butter-smooth effects on the Dribbble Global Design Survey 2019 page.
We start with when to use Framer Motion and why, then build a parallax box, scale and fade-in-up boxes with stagger, page transitions with AnimatePresence and React Router, two sliders, and a progress circle and bar. Each tutorial consists of 1–3 components with a list of props, and all examples are interactive.
When to use Framer Motion and why
Framer Motion is capable of powering animations in Framer X, a prototyping tool, which makes the hand-off extremely convenient. The majority of designers have suffered a situation when they spend ages perfecting every little detail of design only to have it lost in the development process. Framer Motion lets you use the same animation library both in prototyping and production. This way you don’t need to worry your React animations are different from what you’ve intended them to be.
As for the best way to use animation as an instrument in general, the main thing is to keep it meaningful and relevant to the subject. You can also find a dozen useful tips on optimizing complex animations in a separate article. And now, let’s move on directly to our React animations tutorial.
Framer Motion tutorials
Framer Motion is great for animations. Let’s try doing some of those! If at the moment you’re at the beginning of your JavaScript journey, then you’d be better off with simpler things first.
Each Framer Motion tutorial consists of 1-3 components with a list of props (most of which are optional and/or have default values). All examples are interactive, so refresh and click, drag, flip.
Parallax Box Tutorial
ParallaxBox component animation is set in motion by scrolling, imitating the parallax effect. Scroll the triggers component to shift up/down (depending on the scroll direction) by the value specified in the yOffset prop (px, > 0, by default = 100).
MotionValues are used to track the state and speed of an animating value.
Usually automatically calculated MotionValues is more than enough for most cases. But for more advanced ones, you can create them manually, and then inject them into components. We’ll do just that.
To animate the ParallaxBox component, use the chain of MotionValues, that are passed to the ParallaxBox via the useTransform hook (useTransform (parent, from, to, options)).
const y = useTransform(
scrollY,
yRange,
[0, -yOffset],
easing
);
useTransform creates a MotionValue that transforms the output of another MotionValue by mapping it from one range of values into another.
The first parameter, parent: the MotionValue to transform the output of.
We’ll use another hook as it’s value:
const { scrollY } = useViewportScroll();
useViewportScroll(): ScrollMotionValues – provides MotionValues that update when the viewport scrolls. scrollY – vertical scroll distance in pixels.
Input values – from: number[] – a linear series of numbers (all either increasing or decreasing).
const yRange = [transformInitialValue, transformFinalValue];
yRange accepts an array consisting of transformInitialValue – the initial position of the element and transformFinalValue – its position at the end of the animation.
Output values – to: T [] – a series of numbers, colors, or strings. Must be the same length as inputRange.
In the example, the output values take an array:
[0, -yOffset]
where 0 – initial position, –yOffset – element offset (the value is negative since the component is shifted upward relative to its initial position).
The last value that useTransform takes – options – can take several values, but in this example, the most significant for us is ease: EasingFunction []
easing = [0.42, 0, 0.58, 1],
Easing functions are algorithms that let you control the animation speed to give them the desired effect like bouncing, deceleration, etc.
That is, in real life things don’t start and stop moving abruptly and in a linear fashion. Momentum and other physical aspects play their part. For example, when you play ball you keep in mind that it doesn’t move at a constant speed but bounces. A car decelerates while turning, etc. Easing functions help make effects look more natural and life-like.
Easing can take either an array of four numbers [n, n, n, n] known as a cubic-bezier function or built-in named functions like “linear”, “easeInOut”, etc.
Now all that remains is to pass the y value into the component:
return (
<MotionBox ref={ref} initial={{ y: 0 }} style={{ y, opacity }}>
{children}
</MotionBox>
);
Job’s done! Now the component will smoothly shift up while scrolling, imitating the parallax effect.
You can also pass props into the component:
- yOffset – offset value
- easing – animation type
- triggerPoint – a value between 0 and 1, which determines when the animation of this element is to begin, depending on its position on the page, where 0 is the top of the page and 1 is its bottom.
- fadeOut is a boolean value that determines whether the element’s fading out will affect its opacity level.
All listed props are optional and already have default values specified in the component.
Easy, isn’t it?
Intersection Observer | Scale Box Tutorial
This tutorial is much simpler than the previous one. The only difficulty is using a hook from a third-party library react-use – useIntersection – React sensor hook that tracks the changes in the intersection of a target element.
With the help of this hook, we can create the IntersectionObserver component that senses when the motion component appears in its scope and starts the animation.
This component can take a boolean prop reset, with default = false, which is responsible for whether the animation will be triggered when the element appears in the viewport again.
Now we can wrap one or more motion components with the IntersectionObserver component:
<IntersectionObserver>
<ScaleBox />
</IntersectionObserver>
And pass the inView value to the motion component from the context of the IntersectionObserver component:
const { inView } = useContext(IntersectionContext);
Let’s move on to the motion component – ScaleBox.
A scale value allows you to reduce or increase the size of an element. That is, when the ScaleBox falls into the visible part of the browser window, IntersectionObserver changes the value from the inView context to true, which will trigger the animation:
animate={inView ? "show" : "hidden"}
ScaleBox can be used outside the IntersectionObserver component, but then the animation will start after the page loads and will be processed regardless of whether it is in the visible area or not. Often, this is not the way we would like a motion component to act, this is why we need the IntersectionObserver.
To describe the ScaleBox animation we use Variants – sets of pre-defined target objects:
const variants = {
hidden: {
scale: 0,
opacity: 0,
transition
},
show: {
scale: 1,
opacity: 1,
transition
}
};
The Variants object contains key-value pairs, where the keys (labels) are names for the animation properties (in our case, it is “hidden” and “show”, although the names can be anything. The main thing is to keep them meaningful).
The only thing that remains is to pass the variants object to variants prop:
return (
<MotionBox
initial="hidden"
animate={inView ? "show" : "hidden"}
exit="hidden"
variants={variants}
>
{children}
</MotionBox>
);
Variants can set an animation target that is indicated by its labels (for example, initial=”hidden”).
It is also worth considering in more detail the transition property, with which you can set animation execution parameters like duration (s), delay (s), ease.
const transition = {
duration: 0.4,
delay: 0.2,
ease: "easeInOut"
};
There are other parameters that can be passed to transition, for example, loop, the number of iterations of the animation (accepts a number or Infinity).
As a result, we got two components. The first – Intersection Observer – detects the presence of the motion element on the screen. The second one – ScaleBox – changes the element’s size. Both components are quite simple. Using them together allows animating the content appearing on the page when it is displayed in the user’s viewport.
Fade-in-up Box | Stagger Tutorial
FadeInOutBox – a component that animates the appearance of an element, its shift from bottom to top (yOffset) and its opacity.
Like ScaleBox, this component uses variants:
const variants = {
hidden: {
y: yOffset,
opacity: 0,
transition
},
show: {
y: 0,
opacity: 1,
transition
}
};
Only in this case, the y property (transforming an element’s position along the y-axis) is animated, not the scale.
The second component of this example is StaggerWrap animating the nested motion components sequentially with a certain delay (staggerChildren).
const variants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.5
}
}
};
When prop helps to detail the association between parent and its children (false by default). It also can take the “beforeChildren” value if the parent’s animation has to execute before the children’s one or “afterChildren” for the opposite case.
In this case, the animation parameters are transferred to the parent, that is, to the StaggerWrap component itself:
return (
<StaggerContext.Provider value={{ stagger: true }}>
<MotionBox
initial="hidden"
animate="show"
exit="hidden"
variants={variants}
>
{children}
</MotionBox>
</StaggerContext.Provider>
);
};
The only thing left is to pass the variants object into the props options of the children elements:
return stagger ? (
<MotionBox variants={variants}>
{children}
</MotionBox>
) : [...]
Using StaggerWrap together with FadeInUpBox allows you to sequentially animate the appearance of multiple elements with a shift on y (yOffset) and an opacity change.
AnimatePresence | routing Tutorial
With Framer-motion’s component AnimatePresence and React-Router, we can set up beautiful and seamless transitions between pages to boost up your project’s looks ✨
Framer-motion already has a demo with routing, but it’s a bit uneven and might be difficult to understand having no explanations in the docs. So we decided to figure it out with our own little demo and tutorial.
Basically, this demo consists of the main blog page (Blog component) with a blog posts list (PostPreview) and individual post pages (Post). You can navigate to posts by clicking the Learn more link and return back by clicking the Back to Home page link. Navigating between the pages triggers enter and exit animations of components inside AnimatePresence.
First of all, we need to set up routing, if you are not familiar with the subject, please check out docs: Basic Routing (react-router-dom).
<Router>
<Route
render={({ location }) => (
<AnimatePresence exitBeforeEnter initial={false}>
<Switch location={location} key={location.pathname}>
<Route exact path="/" component={Blog} />
<Route exact path="/post/:id" component={Post} />
</Switch>
</AnimatePresence>
)}
/>
</Router>
In a nutshell, Router works something like this: depending on location, value Switch renders the first child Route that matches the current location. If its Home page location is “/” and if it’s one of the posts it is “/post/:id” (“/post/0”, “/post/1”, etc).
To actually render a component inside Router we need to pass it to Route as a prop:
<Route {...} component={Component} />
Or wrap a component with it like this:
<Route {...} ><Component /></Route>
Also, we need to wrap Switch with Framer-motion’s AnimatePresence to enable animation even if our components are removed from the React tree.
Let’s take a closer look at props that we need to pass to AnimatePresence:
- exitBeforeEnter ensures that components will only render one at a time, so the exiting component will finish its exiting animation before the entering component is rendered;
- initial={false} is disabling initial animation when the component is first rendered (i. e. when a page is loaded the first time).
The next step is to create our components. We’ll start with Blog:
const blogVariants = {
enter: { transition: { staggerChildren: 0.1 } },
exit: { transition: { staggerChildren: 0.1 } }
}
const Blog = () => (
<motion.div
initial="initial"
animate="enter"
exit="exit"
variants={blogVariants}
>
{content.map((post) => (
<PostPreview key={post.id} {...post} />
))}
</motion.div>
);
staggerChildren helps us create staggered orchestrated animation (which means that each next item in a list has 0.1 delay from the previous one), but you can remove variants={blogVariants} if you don’t need it.
Next, let’s look at our PostPreview component that we map inside Blog. We wrap up our content with motion.div and passing variants with our animation values.
Also, we need to add Link to navigate to our post pages, let’s pass the to prop that takes the route path, make sure to add id (to={/post/${id}}) of our post to ensure that routing works properly.
const transition = { duration: 0.5, ease: "easeInOut" };
const postPreviewVariants = {
initial: { x: "100%", opacity: 0 },
enter: { x: 0, opacity: 1, transition },
exit: { x: "-100%", opacity: 0, transition }
};
const PostPreview = ({ id, {...} }) => (
<motion.div variants={postPreviewVariants}>
{...}
<Link to={`/post/${id}`}>Learn more</Link>
</motion.div>
);
And last but not least — the Post component:
const transition = { duration: 0.5, ease: "easeInOut" };
const postVariants = {
initial: { y: 100, opacity: 0 },
enter: { y: 0, opacity: 1, transition },
exit: { y: -100, opacity: 0, transition }
};
const Post = ({ match }) => {
const id = Number(match.params.id);
const { title, ... } = content[id];
return (
<motion.div
initial="exit"
animate="enter"
exit="exit"
variants={postVariants}
>
<Link to="/">Back to Home page</Link>
{...}
</motion.div>
);
};
A match object contains information about how our route path matches the URL. With match, we can access many useful properties, but the only one that is relevant in our case is post id (match.params.id) which we need to access the content of the current post.
We also define our animation (postVariants) and pass it to variants of motion.div.
The last step is to add Link to enable navigation back to the Home page.
That’s all! Now each time we navigate between pages the exit animation is triggered, making the process smooth.
Drag Slider
This component uses the useMotionValue hook to change the value of x (that is, for the slider flipping) to a drag gesture (drag = ”x”).
Using the IntersectionObserver, FadeInOutBox, and ScaleBox components we already know, you can pass “scale” | “fadeIn” values to slideApperance props to add animation to the appearance of slides.
This slider is far from perfect: overflow-x: hidden on slider wrapper makes it impossible to implement scroll on mouse wheel motion.
Also, it often gets reset on the last slide to its original position for some yet unknown reason.
Motion Slider
Another slider that works not only on drag but also on controls (arrows and bullets).
In order to animate the state of the slide on unmount, we need to use the AnimationPresence component and pass exitBeforeEnter prop to correctly finish its exit animation before the next component render.
It’s not a perfect example. In particular, bullet animation only works in one direction and uses the third-party library.
Progress Circle and Progress Bar
These are just fooling around with some animations on the Dribbble’s Global Design Survey page and trying to make something similar.
Some components for displaying statistical information. Best work in combination with IntersectionObserver.
Fade In Up Box | Scale Box
Another take on Dribbble.
There are already familiar StaggerWrap components in this example, FadeInUpBox for animating text, and ScaleBox for animating images. In general, the animation is quite simple, but it looks impressive and interesting.
FAQ
What is Framer Motion?
Framer Motion is an open-source React animation library for production-ready animation. It has a low-level declarative API, works for the web and mobile apps, and is available as a separate package for React apps. The same library powers animations in the Framer prototyping tool, so designers and developers can use one animation library in both prototypes and production.
When should you use Framer Motion?
Use Framer Motion when you build a React project and need anything from simple smooth transitions to advanced animation sequences with as little code as possible. It is especially convenient for design hand-off, since prototype animations don’t get lost in development. The main drawback is documentation: a significant part of the functionality is poorly described, so expect to spend time learning.
How do you make a parallax effect with Framer Motion?
Combine the useViewportScroll and useTransform hooks. useViewportScroll provides scrollY, the vertical scroll distance in pixels, and useTransform maps it from an input range to an output range, such as from 0 to a negative yOffset, so the element shifts up while the page scrolls. In the ParallaxBox component, yOffset defaults to 100 px, and an easing function makes the movement look natural.
How do you start an animation when an element enters the viewport?
Wrap the motion components in an IntersectionObserver component built on the useIntersection hook from the react-use library. When the element appears in the visible part of the browser window, the inView value changes to true and triggers the animation, for example scaling up a ScaleBox. A reset prop decides whether the animation plays again each time the element reappears.
How do you animate page transitions with Framer Motion?
Wrap React Router’s Switch in Framer Motion’s AnimatePresence component, which keeps exit animations working even when components are removed from the React tree. Pass exitBeforeEnter so the old page finishes its exit animation before the new one renders, and initial={false} to disable the animation on the first page load. Then give each page’s motion.div its own variants.
What are variants in Framer Motion?
Variants are sets of pre-defined animation targets with labels such as “hidden” and “show”. You pass the object to the variants prop and refer to the labels in props like initial and animate. Variants also let a parent orchestrate its children: with staggerChildren, each next item in a list starts with a delay, for example 0.1 seconds after the previous one.
To wrap up
In general, Framer Motion is a multifunctional, flexible, and modern React animation library. Motion is a flexible tool that is great both for beautifully smooth and simple React animations and for more advanced sequences. All with as little amount of code as possible. Its main issue is that a significant part of its functionality is either poorly and fragmentarily described in the documentation, or not described at all. Because of that, you might spend time learning things instead of implementing them.
Happy if this Framer Motion tutorial helps you to figure out how to build your own motion components with stunning animations and make your job at least a bit easier.
Related reading
- Angular vs React Comparison: Understanding Which Framework to Choose
- Let’s Make A Movie App: Tips From A Front-End Developer
- React vs Next.Js Which Framework to Choose for Front End in 2026
* * *
This Framer Motion tutorial was originally published in January 2020 and was updated in December 2020 to make it more relevant and comprehensive.
