Guide to creating animations that spark joy with Framer Motion

December 15, 2020

/ 21 min read /

0 Likes β€’

0 Replies β€’

0 Reposts

Last Updated December 19, 2020

Over the past few months, Framer Motion went from being a fun tool I played with on the side to a core element of my frontend projects when it comes to adding a layer of interaction to my UIs. I went from knowing almost nothing about animations and transitions, to being able to orchestrate more complex animations involving lots of elements.

I've shared a lot of the animation work I sprinkled throughout my blog on Twitter, and a lot of you have asked me to share more code snippets. Thus I felt it was time for a little write-up!

In this post, you'll find a condensed guide containing everything I've learned when it comes to Framer Motion, the key concepts of animation, and how to use this library to create animations that spark joy through some interactive examples and widgets.

To illustrate the concepts we will see in this blog post, which are very visual, I included a series of editable code snippets/playgrounds and widgets to allow you to try out some of the core features of Framer Motion within the article itself! The aim was to help the reader understand the concepts by applying them, tweaking some code, visualizing examples.

Regarding the interactive code snippets: You can edit the code to tweak the animation at will, and see the resulting animation on the left pane (top if you're on mobile).

Let me know what you think of these examples and whether you've learned these Framer Motion concepts faster by doing!

Anatomy of an animation

First, let's take a look at the main elements that define an animation. When working on one, whether it's to move an element, changing its shape, or color, I always try to answer the following 3 questions:

  1. "Where/how is my element at the beginning?" i.e the initial state
  2. "Where it needs to go or wich shape it needs to take by the end?" i.e. the target state
  3. "How it's going to transition from the initial state to the end state?" i.e. the transition state

In the case of Framer motion, the library gives us a motion component which takes 3 properties (props) that let us define an answer to the 3 questions above:

  • initial: the state of our element at mount time.
1
<motion.div
2
...
3
initial={{
4
x: 0,
5
rotate: 45,
6
}}
7
...
8
/>
  • animate: the state in which our element will be at the end of the animation.
1
<motion.div
2
...
3
animate={{
4
x: 50,
5
rotate: 270,
6
}}
7
...
8
/>
  • transition: how our element goes from the initial state to the target state. This is where we can define which transition type we want to define, delays, or repetitions of the same transition.
1
<motion.div
2
...
3
transition={{
4
ease: "easeIn",
5
duration: 0.7,
6
}}
7
...
8
/>

There are many types of transitions available in Framer Motion so I added this little comparative visualization below for you to see the little nuances between some of the main types and tweak their respective options:

Spring
1
<motion.div
2
...
3
transition={{
4
type: 'spring',
5
stiffness: 100,
6
mass: 3,
7
damping: 1,
8
}}
9
/>
10
Tween
1
<motion.div
2
...
3
transition={{
4
type: 'tween',
5
ease: 'easeInOut',
6
duration: 2,
7
...
8
}}
9
/>
10
Inertia
1
<motion.div
2
...
3
transition={{
4
type: 'inertia',
5
velocity: 50,
6
}}
7
/>
8
9

You can find the complete list of types and all their respective options in this section of the documentation.

Good to know:

πŸ‘‰ You can substitute the animate prop for one of the more specific gesture props like whileHover or whileTap. They can take the same "animation object" we just saw.

πŸ‘‰ Only one of animate or any of the gesture props is required to define an animated Framer Motion component.

πŸ‘‰ The library provides smart defaults for initial and transition when they are not defined. It will even adapt the transition type (spring, tween, ease) based on which property you set in your animate prop!

Now that we went through the basics, let's take a look at our first examples! Below you will find a series of animated components that you can edit and tweak at will. As for what to tweak, the following list contains a few interesting points that you can check out:

  • remove the transition prop from the first component (Example1). Notice that this translation animation went from an ease type to a spring type. This comes from the "smart defaults" we just mentioned.
  • combine animations in Example2: change the second animation from a simple rotation to a rotation and a translation.

I added hints in the comments of the code to guide you. πŸ˜„

Example 1

Example 2

Want to go a bit further before jumping into the next part? Here's a link to the related doc: How to animate in Framer Motion.

Using variants

Now that we've seen and tweaked our first Framer Motion based components, you might notice that, in the case of complex animations, things can quickly get messy. Defining everything inline can lead to your motion components being fairly hard to read but also a bit repetitive.

This is why one of my favorite features of Framer Motion is the ability to define animations in a declarative way through variants.

Variants are sets that have predefined animation objects, the kind of object we passed in the examples above in the animation prop.

The following is an example showcasing how you can leverage variants. Notice how we declared a set of variants within the buttonVariants object and how the respective keys of these variants are referenced in the motion component:

Using variants with the motion component

1
import { motion } from 'framer-motion';
2
3
const AnimatedButton = () => {
4
const buttonVariants = {
5
hover: {
6
scale: 1.5,
7
},
8
pressed: {
9
scale: 0.5,
10
},
11
rest: {
12
scale: 1,
13
},
14
};
15
16
return (
17
<motion.button
18
initial="rest"
19
whileHover="hover"
20
whileTap="pressed"
21
variants={buttonVariants}
22
>
23
Click me!
24
</motion.button>
25
);
26
};

After seeing these variants the first time, like me, you might be wondering "wait, if everything is predefined, how can I make my animations based on some dynamic property?"

Well, don't you worry! Framer Motion lets you also define variants as functions. Each variant as a function can take one argument and return and animation object. That argument has to be passed in the custom prop of your motion component. The example below showcases an example of variant as function, the hover variant will return a different object whether the button is clicked or not. The state of the button isClicked is passed in the custom prop of the motion component.

Using variants and the custom prop with the motion component

1
import { motion } from 'framer-motion';
2
3
const AnimatedButton = () => {
4
const buttonVariants = {
5
// any variant declared as a function will inherit the `custom prop` as argument
6
hover: (clicked) => ({
7
// once clicked the button will not scale on hover anymore
8
scale: clicked ? 1 : 1.5,
9
}),
10
pressed: {
11
scale: 0.5,
12
},
13
rest: {
14
scale: 1,
15
},
16
};
17
18
const [clicked, setClicked] = React.useState(false);
19
20
return (
21
<motion.button
22
initial="rest"
23
whileHover="hover"
24
whileTap="pressed"
25
variants={buttonVariants}
26
custom={clicked}
27
onClick={() => setClicked(true)}
28
>
29
Click me!
30
</motion.button>
31
);
32
};

Now that we know what variants are, let's try to work with them in the following playground. Let's try to:

  • make the first button scale on hover (for now, it only rotates).
  • make the button not scale back to its original size if it's been clicked on. Hint: you can use the custom prop we just mentioned above πŸ’‘.

Like in the first part, I left comments in the code to guide you!

Advanced animations using Motion Values

At this point, we know how to use the key features of Framer Motion to start building our own animations:

  • we know the main elements that define an animation βœ…
  • we know how to use variants to define animations in a declarative way βœ…

With those newly acquired skills, we can now look at on more concept that will allow us to build more advanced animations: Motion Values. In this part we will learn what are Motion Values and how to use them and also looked at a practical example to illustrate this concept: my own "Copy To Clipboard" button!

Motion Values

A MotionValue is an internal value to the Framer Motion library that "tracks the state and the velocity of an animating value". For more complex animation we may want to create our own MotionValue (quote from the docs), and then add them as inline style to a given component. To define a MotionValue, we need to use the useMotionValue hook.

A MotionValue can be practical when you want to have one animation depending on another one. For example, we may want to tie together the scale and the opacity of a component in such a way that, once the component reaches half of its targeted scale, the opacity should be equal to 100%.

To handle that kind of use case, Framer Motion gives us a second hook: useTransform that transforms an input MotionValue to another MotionValue through a function. The example below showcases how you can use these 2 hooks together:

Dissecting the "Copy To Clipboard" animation

You might have noticed that I sprinkled some animated SVG icons for my buttons throughout my blog ✨. One of my favorite is the "Copy To Clipboard" button on my code snippets, so I figured it would a great case study to look at together to illustrate some of the use cases for Motion Values. It uses both useMotionValue and useTransform to ensure that the opacity level of our checkmark icon is a function of its pathLength.

I added a "dissected" version of this component below to let you fully understand what is happening when clicking on the icon and how the Motion Values change throughout the transition. You can tweak the duration with the slider, and also visualize the MotionValue for the opacity and pathLength of the checkmark SVG.

PathLength:
0
Opacity:
0

When clicking on the button, you can see that the more the pathLength increases, the more the opacity of the checkmark increases as well following this function:

1
f: y -> x * 2
2
3
// Where x is the pathLength of our SVG y is the opacity

which is equivalent to the following code using Framer Motion's hooks:

1
const pathLength = useMotionValue(0);
2
const opacity = useTransform(pathLength, [0, 0.5], [0, 1]);

When the pathLength reaches half of its targeted value, the opacity is at 100% and thus the icon is fully visible for the rest of the transition while the pathLength continues to grow.

Here's the code for the full implementation of this component:

Full implementation of the Copy To Clipboard button animation

1
import React from 'react';
2
import { motion, useMotionValue, useTransform } from 'framer-motion';
3
4
const CopyToClipboardButton = () => {
5
const duration = 0.4;
6
7
const clipboardIconVariants = {
8
clicked: { opacity: 0 },
9
unclicked: { opacity: 1 },
10
};
11
12
const checkmarkIconVariants = {
13
clicked: { pathLength: 1 },
14
unclicked: { pathLength: 0 },
15
};
16
17
const [isClicked, setIsClicked] = React.useState(false);
18
19
const pathLength = useMotionValue(0);
20
const opacity = useTransform(pathLength, [0, 0.5], [0, 1]);
21
22
return (
23
<button
24
css={{
25
background: 'transparent',
26
border: 'none',
27
cursor: isClicked ? 'default' : 'pointer',
28
outline: 'none',
29
marginBottom: '20px',
30
}}
31
aria-label="Copy to clipboard"
32
title="Copy to clipboard"
33
disabled={isClicked}
34
onClick={() => {
35
setIsClicked(true);
36
}}
37
>
38
<svg
39
width="100"
40
height="100"
41
viewBox="0 0 25 25"
42
fill="none"
43
xmlns="http://www.w3.org/2000/svg"
44
>
45
<motion.path
46
d="M20.8511 9.46338H11.8511C10.7465 9.46338 9.85107 10.3588 9.85107 11.4634V20.4634C9.85107 21.5679 10.7465 22.4634 11.8511 22.4634H20.8511C21.9556 22.4634 22.8511 21.5679 22.8511 20.4634V11.4634C22.8511 10.3588 21.9556 9.46338 20.8511 9.46338Z"
47
stroke="#949699"
48
strokeWidth="2"
49
strokeLinecap="round"
50
strokeLinejoin="round"
51
initial={false}
52
animate={isClicked ? 'clicked' : 'unclicked'}
53
variants={clipboardIconVariants}
54
transition={{ duration }}
55
/>
56
<motion.path
57
d="M5.85107 15.4634H4.85107C4.32064 15.4634 3.81193 15.2527 3.43686 14.8776C3.06179 14.5025 2.85107 13.9938 2.85107 13.4634V4.46338C2.85107 3.93295 3.06179 3.42424 3.43686 3.04917C3.81193 2.67409 4.32064 2.46338 4.85107 2.46338H13.8511C14.3815 2.46338 14.8902 2.67409 15.2653 3.04917C15.6404 3.42424 15.8511 3.93295 15.8511 4.46338V5.46338"
58
stroke="#949699"
59
strokeWidth="2"
60
strokeLinecap="round"
61
strokeLinejoin="round"
62
initial={false}
63
animate={isClicked ? 'clicked' : 'unclicked'}
64
variants={clipboardIconVariants}
65
transition={{ duration }}
66
/>
67
<motion.path
68
d="M20 6L9 17L4 12"
69
stroke="#5184f9"
70
strokeWidth="2"
71
strokeLinecap="round"
72
strokeLinejoin="round"
73
initial={false}
74
animate={isClicked ? 'clicked' : 'unclicked'}
75
variants={checkmarkIconVariants}
76
style={{ pathLength, opacity }}
77
transition={{ duration }}
78
/>
79
</svg>
80
</button>
81
);
82
};

It might seem dense at first, but you'll notice that it is composed of elements that we've seen individually in the previous sections and examples:

  • variants for the clipboard SVG and the checkmark SVG
1
const clipboardIconVariants = {
2
clicked: { opacity: 0 },
3
unclicked: { opacity: 1 },
4
};
5
6
const checkmarkIconVariants = {
7
clicked: { pathLength: 1 },
8
unclicked: { pathLength: 0 },
9
};
  • useMotionValue and useTransform to intertwine the opacity and pathLength values together
1
const pathLength = useMotionValue(0);
2
const opacity = useTransform(pathLength, [0, 0.5], [0, 1]);

Orchestration

For this last part, we will focus on how to orchestrate animations, especially with the two types of orchestration I used the most when building animations:

  • Delays and repetitions: "move to point A, then 2 seconds later move to point B then repeat"
  • Parent-Children: "parent appears first, then the children one after the other at 1-second interval"

Delays and repetition

This is perhaps the first type of orchestration you'll naturally think about when starting to experiment with more complex animations. Framer Motion lets you not only delay when an animation should kick-off but also delay any repetition of that same animation if needed.

I used delays and repetitions to orchestrate some of the micro-animations you can see in my Guide to CI/CD for frontend developers which were the first fairly complex animated components I implemented.

A few orchestration patterns have already been showcased in some of the previous examples out of necessity, but here's a more detailed example for you to play with:

  • you can try to change the repeat type from mirror to loop and observe the subtle change of repetition type.
  • make the animation repeat indefinitely instead of just 3 times.
  • make the initial delay 2s and every repeat delay 1s, you should observe the animation pausing between each repetition.

Parent-Children

A more advanced pattern for orchestration that I recently discovered is what I named "parent-children orchestration". It is pretty useful when you want to delay the animations of some children components in relation to an animated parent component.

Framer Motion gives us the delayChildren option for our transition object to do just that:

Using delayChildren in a transition

1
const boxVariants = {
2
out: {
3
y: 600,
4
},
5
in: {
6
y: 0,
7
transition: {
8
duration: 0.6,
9
// Both children will appear 1.2s AFTER the parent has appeared
10
delayChildren: 1.2,
11
},
12
},
13
};
14
15
const iconVariants = {
16
out: {
17
x: -600,
18
},
19
in: {
20
x: 0,
21
},
22
};
23
24
return (
25
<motion.div variants={boxVariants} initial="out" animate="in">
26
<motion.span
27
role="img"
28
aria-labelledby="magic wand"
29
variants={iconVariants}
30
>
31
πŸͺ„
32
</motion.span>
33
<motion.span role="img" aria-labelledby="sparkles" variants={iconVariants}>
34
✨
35
</motion.span>
36
</motion.div>
37
);

On top of that, what if we wanted to not only delay the children as a group but also delay each child based on its siblings, such as, make them appear 1s after their previous sibling appeared. Well, we're in luck, because there's an easy way to do that with the staggerChildren

Using delayChildren and staggerChildren in a transition

1
const boxVariants = {
2
out: {
3
y: 600,
4
},
5
in: {
6
y: 0,
7
transition: {
8
duration: 0.6,
9
// The first child will appear AFTER the parrent has appeared on the screen
10
delayChildren: 1.2,
11
// The next sibling will appear 0.5s after the previous one
12
staggerChildren: 0.5,
13
},
14
},
15
};
16
17
const iconVariants = {
18
out: {
19
x: -600,
20
},
21
in: {
22
x: 0,
23
},
24
};
25
26
return (
27
<motion.div variants={boxVariants} initial="out" animate="in">
28
<motion.span
29
role="img"
30
aria-labelledby="magic wand"
31
variants={iconVariants}
32
>
33
πŸš€
34
</motion.span>
35
<motion.span role="img" aria-labelledby="sparkles" variants={iconVariants}>
36
✨
37
</motion.span>
38
</motion.div>
39
);

What these 2 options exactly do might seem confusing at first. I wished I had some visual examples to really get a grasp on how they worked when I got started. I hope the following visualization will do just that!

In the widget below, you can tweak the values of beforeChildren and staggeredChildren and see how the resulting transition.

πŸš€βœ¨πŸŽ‰

I used this type of orchestration to power the list of people who've shared or liked my articles that you can see at the end of each blog post. It's a component that quite a few people like, so I thought I could use it as a little example for you to interact and have fun with:

Already 9 furry friends liked this post!

  • 🐢
  • 🐱
  • 🐰
  • 🐭
  • 🐹
  • 🦊
  • 🐻
  • 🐼
  • 🐨

Conclusion

Wow, we just learned a lot of stuff about Framer Motion! We went from building very basic animations like translations to orchestrate more complex ones involving multiple components and also tie together multiple transitions using useMotionValue and useTransform. You have now learned pretty much everything I know about Framer Motion and can start sprinkling some amazing animations in your own frontend work.

This is my first time trying out this format involving interactive widgets and playgrounds to illustrate what I've learned, let me know what you think! Would you like to see more articles like this one? How would you improve the widgets and examples? I'm always looking to push this blog forward and would love to get some feedback.

Did you come up with some cool animations after going through this guide?

Don't hesitate to send me a message showcasing your creations!

Want to see more?

Here are some other Framer Motion related articles or examples I came up with:

Fetching Replies...

Do you have any questions, comments or simply wish to contact me privately? Don’t hesitate to shoot me a DM on Twitter.


Have a wonderful day.
Maxime

Subscribe to my newsletter

Get email from me about my ideas, frontend development resources and tips as well as exclusive previews of upcoming articles.


Β© 2021 Maxime Heckel β€”β€” SF/NY