DEV Community

Cover image for Framer Motion - beautiful animations and interactions for React. 🤤
Vaibhav Khulbe
Vaibhav Khulbe

Posted on • Updated on

Framer Motion - beautiful animations and interactions for React. 🤤

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:

  1. Animations (CodeSandbox demo)
  2. Variants. (CodeSandbox demo)
  3. Gestures. (CodeSandbox demo)
  4. Drag. (CodeSandbox demo)
  5. Scroll. (CodeSandbox demo)
  6. Path. (CodeSandbox demo)

My favorite one is the Variants, click below to interact:

Also,

  1. It uses server-side rendering.
  2. It has CSS variables support.
  3. You can unmount animations easily.
  4. It has great accessibility options.
  5. 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 }}
 />
Enter fullscreen mode Exit fullscreen mode

➡ 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 }}
/>
Enter fullscreen mode Exit fullscreen mode

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 }}
/>
Enter fullscreen mode Exit fullscreen mode

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 }} />
}
Enter fullscreen mode Exit fullscreen mode

What we'll be making? 😏

Tutorial demo

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:

  1. First, when the page loads, it fades in. The only animation happening.
  2. 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.
  3. We can also click and drag the React logo horizontally and it keeps fading as it moves away from the center.
  4. When hovered, the text below the logo scales up.
  5. To move the text from its position horizontally, we have a slider from which it can be controlled.
  6. 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
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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 with min and max values as -100 and 100 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>
Enter fullscreen mode Exit fullscreen mode

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 the animate.
  • 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 }}
Enter fullscreen mode Exit fullscreen mode

Now the entire page fades!

Page fade animation

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 to false 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]);
Enter fullscreen mode Exit fullscreen mode

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>
.
.
.
Enter fullscreen mode Exit fullscreen mode

Now this interaction works!

React logo interaction

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);
Enter fullscreen mode Exit fullscreen mode

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>
.
.
.
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

And now our final interaction also works!

Text interactions


If you're not impressed by this library then you can check out React Spring. I wrote a tutorial regarding the same:


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

Image source: https://t.co/GsUA5a7mlf#DevHumour pic.twitter.com/WQfKOWTjfa

— Microsoft Developer UK (@msdevUK) October 15, 2020

📫 Subscribe to my weekly developer newsletter 📫

PS: From this year, I've decided to write here on DEV Community. Previously, I wrote on Medium. If anyone wants to take a look at my articles, here's my Medium profile.

Oldest comments (6)

Collapse
 
steelwolf180 profile image
Max Ong Zong Bao

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.

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

Yes, we gave it constraints for the x axis!

Collapse
 
ekeijl profile image
Edwin • Edited

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.

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

This is already in my drafts! Not sure when I'll publish it but I will! Thank you for the idea and the pointers :)

In draft

Collapse
 
miguelangeltorresfp profile image
Miguel Angel Torres FP

Maybe you can take a look at this project.
Although it is a bit outdated.

react-animation-comparison

Collapse
 
vaibhavkhulbe profile image
Vaibhav Khulbe

Wow, that's a really cool repo. Thanks for sharing Miguel!