Framer Motion is a relatively new and popular open-source React animation library, aimed at creating production-ready animation. Framer Motion is Pose’s animation library next-in-line. It possesses a low-level declarative API and can be used irrespective of platform, for the web as well as for mobile apps. 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. However, if you are working with more sophisticated cases, there’s too little information on the web in this respect. So it makes no sense to delve into the simplest examples, they can be done according to the documentation. There are also articles on this topic (albeit not very many of them) on the web. Let’s tinker with more complex Framer animations instead.
If you’ve ever wondered how to make this or that butter-smooth effect like, for example, on the Dribbble Global Design Survey 2019 page, then read on and learn from this Framer Motion tutorial!
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 grasp the main idea and a dozen useful tips in this 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.
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.
* * *
This Framer Motion tutorial was originally published in January 2020 and was updated in December 2020 to make it more relevant and comprehensive.