Framer Motion Tutorials: Make More Advanced Animations
There are simple Framer Motion tutorials, but what about more complex ones? Learn to make more advanced React animations in Framer Motion from this guide.
Join the DZone community and get the full member experience.
Join For FreeFramer 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 theyOffset
prop (px, >0
, by default =100
).MotionValues
are used to track the state and speed of an animating value. Usually automatically calculatedMotionValues
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 theParallaxBox
component, use the chain of MotionValues
, that are passed to theParallaxBox
via the useTransform
hook (useTransform (parent, from, to, options)
).
const y = useTransform(
scrollY,
yRange,
[0, -yOffset],
easing
);
useTransform
creates aMotionValue
that transforms the output of another MotionValue by mapping it from one range of values into another.
The first parameter, parent: theMotionValue
to transform the output of.
We’ll use another hook as it’s value:
const { scrollY } = useViewportScroll();
useViewportScroll()
: ScrollMotionValues
providesMotionValues
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 oftransformInitialValue
, 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 asinputRange
.
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 thatuseTransform
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 they
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 theIntersectionObserver
component that senses when the motion component appears in its scope and starts the animation.
This component can take a boolean prop reset, withdefault = 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 theIntersectionObserver
component:
<IntersectionObserver>
<ScaleBox />
</IntersectionObserver>
And pass theinView
value to themotion
component from the context of theIntersectionObserver
component:
const { inView } = useContext(IntersectionContext);
Let's move on to the motion component,ScaleBox
. Ascale
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 theinView
context totrue
, 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 theScaleBox
animation we usevariants
, sets of pre-defined target objects:
const variants = {
hidden: {
scale: 0,
opacity: 0,
transition
},
show: {
scale: 1,
opacity: 1,
transition
}
};
Thevariants
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 thevariants
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 totransition
, for example,loop
, the number of iterations of the animation (accepts anumber
orInfinity
).
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.
LikeScaleBox
, this component uses variants
:
const variants = {
hidden: {
y: yOffset,
opacity: 0,
transition
},
show: {
y: 0,
opacity: 1,
transition
}
};
Only in this case, they
property (transforming an element’s position along the y-axis) is animated, not the scale.
The second component of this example isStaggerWrap
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 the 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 theStaggerWrap
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 thevariants
object into the props options of the children elements:
return stagger ? (
<MotionBox variants={variants}>
{children}
</MotionBox>
) : [...]
UsingStaggerWrap
together withFadeInUpBox
allows you to sequentially animate the appearance of multiple elements with a shift ony
(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 insideAnimatePresence
. 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 onlocation
, valueSwitch
renders the first childRoute
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 acomponent
with it like this:
<Route {...} ><Component /></Route>
Also, we need to wrapSwitch
with Framer-motion'sAnimatePresence
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 toAnimatePresence
:
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 withBlog
:
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 a 0.1 delay from the previous one), but you can removevariants={blogVariants}
if you don't need it.
Next, let's look at ourPostPreview
component that we map insideBlog
. We wrap up our content withmotion.div
and passing variants with our animation values.
Also, we need to addLink
to navigate to our post pages, let's pass theto
prop that takes the route path, make sure to addid (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, thePost
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. Withmatch
, 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 addLink
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 theuseMotionValue
hook to change the value ofx
(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 toslideApperance
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 theAnimationPresence
component and passexitBeforeEnter
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 familiarStaggerWrap
components in this example,FadeInUpBox
for animating text, andScaleBox
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. Written by Julia Shikanova and Kate Shokurova.
* * *
This Framer Motion tutorial was originally published in January 2020 and was updated in December 2020 to make it more relevant and comprehensive.
Published at DZone with permission of Kate Shokurova. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments