Previously, I covered React Spring, a spring-physics based animation library. We made some easy to make toggle and then a somewhat complex animation/transition on hover.
Well, that was all good and cool until I came up with Framer Motion! This is a really great library to make your prototypes come to life with double the ease of use from React Spring.
Let's take a look at what it has to offer and why you should be using it in your next React project.
What is Framer Motion? 🤔
Framer Motion is a production-ready motion library for React. It utilizes the power of the Framer prototyping tool and is fully open-source.
There are some out-of-the-box features or selling points:
- Animations (CodeSandbox demo)
- Variants. (CodeSandbox demo)
- Gestures. (CodeSandbox demo)
- Drag. (CodeSandbox demo)
- Scroll. (CodeSandbox demo)
- Path. (CodeSandbox demo)
My favorite one is the Variants, click below to interact:
Also,
- It uses server-side rendering.
- It has CSS variables support.
- You can unmount animations easily.
- It has great accessibility options.
- You can handoff designs from Framer to Framer Motion.
Before we do any of this we need to understand some basics, most probably its API.
The Motion API 🔌
This is what the heart of Framer Motion is. It provides us with a variety of options including those which you saw in the above points.
➡ The motion
component.
It's a React component built-into the library and is available for almost any of the HTML or SVG element you'll be using in your UI. These are DOM optimized for 60fps animation and gesture support.
What's good is that we can easily convert a static HTML/SVG element to a motion component. For example, if we have a usual div
, then just add motion.
in front of that HTML tag and you have a motion
component! Hence <div>
becomes <motion.div>
.
It allows you to:
- Declaratively or imperatively animate components.
- Animate throughout React trees via variants.
- Respond to gestures with animations.
- Add drag, pan, hover, and tap gestures.
Here's an example:
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2 }}
/>
➡ The animation
prop.
As you saw in the above code snippet, motion
components are animated via the animate
prop. When any value in animate changes, the component will automatically animate to the updated target.
If you use x
or scale
values then they will be animated via a spring simulation. Whereas values like opacity
or color
will be animated with a tween.
You can set different types of animation by passing a transition
prop.
Here's an example:
<motion.div
animate={{ x: 100 }}
transition={{ ease: "easeOut", duration: 2 }}
/>
➡ Gestures.
All the motion
components can detect hover, tap, pan, and drag gestures. Each of these has event listeners you can attach.
Two of the commonly used gesture props provides by the motion
component are whileHover
and whileTap
.
Here's an example:
motion.button
whileHover={{
scale: 1.2,
transition: { duration: 1 },
}}
whileTap={{ scale: 0.9 }}
/>
➡ MotionValue.
This is used to track the state and velocity of animating values. These are created automatically. But for advanced use-cases, it is possible to create them manually.
It allows you to:
- Set and get the state.
- Chain MotionValues via the
useTransform
hook. - Pass to multiple components to synchronize motion across them.
Here's an example:
export function MyComponent() {
const x = useMotionValue(0)
return <motion.div style={{ x }} />
}
What we'll be making? 😏
Yes! We're taking the boilerplate interface that comes when we create a React app and adding a little bit of interaction fun to it. As you can see, these are a few of the things happening:
- First, when the page loads, it fades in. The only animation happening.
- Next comes the interactions. When the React logo is clicked, we see it acts as a button. It pushes back on mouse press and when released, it comes to its normal state.
- We can also click and drag the React logo horizontally and it keeps fading as it moves away from the center.
- When hovered, the text below the logo scales up.
- To move the text from its position horizontally, we have a slider from which it can be controlled.
- Finally, we can fade in and out the same text with the toggle button.
There's so much to cover. Let's dive straight into the development!
Step 1: Create a React project and add Framer Motion
After you're done with creating a React app, simply install the Framer Motion dependency with the following command:
npm i framer-motion
Step 2: Import the library and convert the elements!
For this demo, we need to import
these three API functions: motion
, useMotionValue
, useTransform
.
import { motion, useMotionValue, useTransform } from 'framer-motion';
We already talked about the first two. Now the useTransform
is a hook through which we can pass the latest MotionValue through an update function that takes the latest parent value and transforms it.
After the import, we need to change some of the default HTML tags that come with React boilerplate to the new Framer Motion ones. Do the following changes in App.js:
- Parent
<div>
element to<motion.div>
. - Wrap the React logo
<img>
tag inside a newly created<motion.div>
. - The
<p>
tag to<motion.p>
. - Add a new
<input>
element which will be our range slider withmin
andmax
values as-100
and100
respectively. - Next to this create a new
<motion.button>
with the text as "Toggle Fade".
Here's what we did so far:
<motion.div className='App'>
<header className='App-header'>
<motion.div>
<img src={logo} className='App-logo' alt='logo' />
</motion.div>
<motion.p>
Edit <code>src/App.js</code> and save to reload.
</motion.p>
<input
type='range'
name='range'
min='-100'
max='100'
/>
<motion.button className='toggle-button'>
Toggle Fade
</motion.button>
</header>
</motion.div>
Nothing will happen yet as we haven't written any props and the Motion code doesn't have its properties to work on.
Step 3: Add the animations and transitions!
The page fade animation:
For the initial fade animation, we use the initial
, animate
, and transition
properties over the new motion.div
.
- The
initial
initializes a value for theanimate
. - The
animate
has the actual values to animate to - The
transition
is used to add a default transition of one frame change to another.
As we need a simple fade animation where the animation takes place half a second, we give the following properties to the motion.div
:
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
Now the entire page fades!
The tap and drag interaction on React logo:
This is achieved by the whileTap
and drag
helper animation props on the motion
component.
- The
whileTap
animates while the element is pressed/clicked. - The
drag
enables the dragging gesture of an element and is set tofalse
by default.
So, on tapping the logo, we first need it to scale a bit, hence we add the scale
property to the whileTap
prop and for the drag, we need to pass on which axis dragging is to be done. Here, we do it horizontally so it's the x
axis.
To achieve the actual value of the x
we passed on the drag
prop, we'll be using the useMotionValue
hook which tracks the state and the velocity of the dragged element. Initially, we don't want the drag to enable, hence we pass in 0
.
As for defining the coordinates of how much drag is to be done, the useTransform
hook will help us. Through this, we can pass in the latest value of the x
axis. It can be any number you like depending on the expensiveness of the drag you want to achieve.
const x = useMotionValue(0);
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0]);
Now, for both of these to work, we need to pass the style
prop which takes in the different constants we provided above. Hence out image drag and tap interaction code looks like this:
const x = useMotionValue(0);
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0]);
.
.
.
<motion.div whileTap={{ scale: 0.9 }} drag='x' style={{ x, opacity }}>
<img src={logo} className='App-logo' alt='logo' />
</motion.div>
.
.
.
Now this interaction works!
Interactions on the text:
We have a scale on hover and tap, a drag on moving the slider, and finally a fade toggle using a button to finish off.
The scale is done exactly like we did the tap, it's just that here the interactions are both tap and hover so for the new hover interaction we use the whileHover
prop.
The x
variable is used for the horizontal drag as we need the same values. Now, to constraint its values we can fine-tune it using the dragConstraints
prop which allows us to pass the left
and right
constraints of the drag gesture.
For the final interactions, we need to use the useState
React Hook as we are changing the drag and fade states of the text. Hence, we define the following two variables for states:
const [value, setValue] = useState(0);
const [toggle, setToggle] = useState(1);
On the <input />
element we created at the beginning, its onChange
event uses the setValue()
method from the useState
Hook and we pass in the current value chosen by the user when they drag the slider. A similar event is fired on the <motion.button>
's onClick
but here we're toggling by interchanging the states from 0
to 1
or vice-versa.
For the actual fade to kick in, we simply get the value
from the created state (adding the 'px'
string so that it works as an actual pixel unit) and use the opacity
value equal to the toggle
we created.
const [value, setValue] = useState(0);
const [toggle, setToggle] = useState(1);
.
.
.
<motion.p animate={{ x: value + 'px', opacity: toggle }}
drag='x'
dragConstraints={{ left: -100, right: 100 }}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}>
Edit <code>src/App.js</code> and save to reload
</motion.p>
<input type='range' name='range' min='-100' max='100'
value={value}
onChange={(e) => setValue(e.target.value)} />
<motion.button onClick={() => setToggle((prevValue)
=> (prevValue ? 0 : 1))}
className='toggle-button'>Toggle Fade
</motion.button>
.
.
.
The button styles are simple in CSS to look better from the default one:
.toggle-button {
margin-top: 1.5em;
width: 10em;
border: 0;
outline: none;
padding: 1em;
border-radius: 10em;
font-weight: bold;
}
.toggle-button:hover {
color: #282c34;
background-color: #61dafb;
}
And now our final interaction also works!
If you're not impressed by this library then you can check out React Spring. I wrote a tutorial regarding the same:
Spring it on! The complete guide to React Spring. 🧵
Vaibhav Khulbe ・ Oct 2 '20
Thanks for reading, I appreciate it! Have a good day. (✿◕‿◕✿)
And if you want to impress developers, you can try the new open-source Windows Terminal. 😎 Download here: https://t.co/bZQ78xBBr7
— Microsoft Developer UK (@msdevUK) October 15, 2020
Image source: https://t.co/GsUA5a7mlf#DevHumour pic.twitter.com/WQfKOWTjfa
Top comments (6)
Thank you! This is a great for getting started!
It would be interesting to read a more in depth comparison between Framer Motion and React Spring. What are the benefits, differences and when would you choose one or the other? Maybe try to create the same animations using both libraries and see how they compare in API complexity, bundle size, lines of code.
This is already in my drafts! Not sure when I'll publish it but I will! Thank you for the idea and the pointers :)
Maybe you can take a look at this project.
Although it is a bit outdated.
react-animation-comparison
Wow, that's a really cool repo. Thanks for sharing Miguel!
This is interesting cause I stumble upon but was unsure about how it works. As I was aiming to drag it around in any directions instead of just left to right and top to bottom.
Yes, we gave it constraints for the
x
axis!