Learn Framer Motion with React

Learn Framer Motion with React

First we learn how we got here with CSS, then we build out a React app with Framer animations.

Β·

13 min read

Featured on Hashnode

Before we get into this, there are probably some topics you should already be familiar with. Namely HTML, CSS, JavaScript, and of course React JS. Also we are going to need either NodeJS - version 12.+ or Yarn version 1.22.+ installed on your computer. With that out of the way, let's get going!

Starting out with how we ended up with Framer Motion, and what problems it solves for you.

In the end we will build this cool little app, which allows you to play around with the React Logo using Framer Motion to power the animations.

Are you ready to start animating some things? πŸ˜„

cartoon-bear-does-cartwheel.gif

How we got here, some CSS history πŸ€”

When it comes to animation on the web, we're going to skip the Adobe Flash part and just focus on the CSS side. Let's all just pretend the whole Flash thing didn't happen πŸ˜‰. We are going to be talking about native web animations. Which are the best kind. πŸ‘‘

When it comes to animations in CSS, they are not incredibly complex to get started with. Basically you can just add an animation property to a CSS class. You can then just pass the values you want to the animation, or animations separated by commas. There are 3 main ways to control the speed of the animation as well. These are using Keyword values, Function values, and Steps Function keywords. If you want more information about the built in animation-timing-function's in CSS, you can read about it yourself on MDN.

Now you can use the CSS at-rule @keyframes in order to get more control over your animations. Which is great, because it allows you to more easily piece together more than 1 animation to make the whole process look more natural. Or this allows you to define what will happen at different stages of your animation. Either way it's great, because it allows you more control over your animations. The problem with this system is that it's timing based. Not that timing-based animations are bad, but as time has progressed, we have all come to expect more out of our interactions on the web. πŸŽ† πŸ¦„

Now on to React Land βš›

More recently there have been some great libraries to help out with animations in React. First up is a library called GreenSock/GSAP, which had its first commit on September 21, 2012. I know can you even believe there was a GitHub back then! But there totally was. 🀯

GreenSock made huge strides towards just being able to use a JavaScript function, and pass in an object of config settings. So now developers could use JavaScript inside of React to create amazing animations, instead of having to write the CSS afterwards. GreenSock is also highly performant, and works with SVG animations. It is used by lots of big tech companies, and has 10.8k Stars on GitHub.

All of this is great, and GreenSock has a few Ease settings like elastic and bounce that are kind of physics based. But if you want more control of and physics-based features, then you have to upgrade to a paid plan. As not all of GreenSock is open source.

Building on top of physics-based animation, another library called React Spring was created. It is hosted on GitHub with an MIT Open Source License. πŸ’Ÿ Originally React Spring was Class based, but has been based on React Hooks since version 7. It was released sometime around December, 2018.

Now I'm a big fan of React Spring myself, and I have gladly used it before in personal and professional projects. Plus, the finished result from it is so nice. And by nice I mean the animations are very smooth and highly performant. The problem I find with it is that sometimes the required syntax can feel a little bit overboard. For instance, here's the code required to transition on mount/unmount single-component reveals:

const [show, set] = useState(false)
const transitions = useTransition(show, {
  from: { position: 'absolute', opacity: 0 },
  enter: { opacity: 1 },
  leave: { opacity: 0 },
})
return transitions.map(({ item, key, props }) =>
  item && <animated.div key={key} style={props}>✌️</animated.div>
)

Disclaimer: the style shown here is from an older version of React Spring

See what I'm saying, I mean yes you can remove the state portion by passing it in as props to make the code smaller. But let's just say that looping through an array/object via .map() doesn't feel like the easiest way to achieve this. Or at the very least, not the most beginner friendly way to approach this.

This is where Framer Motion comes in! πŸ₯³πŸŽ‰ Framer Motion is made by the same people who made Pose by Popmotion, which has now been depreciated in favor of Framer Motion. Just like React Spring, Framer Motion is open source and hosted on GitHub with a MIT license. You can find a link to the repo here. πŸ‘

Now armed with all that information, let's get the super awesome fun part! And by that, I mean the code ✨🌈

Let's start building things already!!!

There's pretty much 2 ways to get started with this project. You can just clone my GitHub repository and switch to the starting branch, or you can just build it yourself by running npx create-react-app motion-time.

The finished version with the final code is on the main branch of the same repo.

After that we will be using Yarn version 1.22.x, so make sure the command yarn works in your terminal, by typing yarn -v. You can also use NPM with this project, it should not make a difference.

Now let's install the dependency for our project, framer-motion. If you are using Yarn type:

yarn add framer-motion

Or if you are using NPM:

npm install framer-motion

Next up we are going to import framer-motion into our App.js file located in /src/App.js. Go ahead and open up App.js in your Text Editor. Personally, I use Visual Studio Code, but you can you whatever Text Editor or IDE you like best. πŸ’–

We will go ahead and add Framer Motion on line 2 of App.js. Don't worry we will get back to this file soon, and write some JSX.

import React, { useRef } from "react";
import { motion } from "framer-motion"; // here's the new line
import logo from "./logo.svg";
import "./App.css";

Next we are going to go ahead and remove lines 10-15 and lines 30-38 from /src/App.css

--10 @media (prefers-reduced-motion: no-preference) {
--11  .App-logo {
--12    animation: App-logo-spin infinite 20s linear;
--13  }
--14 }
--15
--30
--31  @keyframes App-logo-spin {
--32    from {
--33      transform: rotate(0deg);
--34    }
--35    to {
--36      transform: rotate(360deg);
--37    }
--38  }

Great so that should take care of removing all the CSS that we no longer need. Here is the CSS that I used to style my project, and make the logo bigger. Feel free to copy and paste this into your own App.css file.

.App {
 text-align: center;
}  

.App-logo {
 height: 40vmin;
 pointer-events: none;
}

.App-main {
 background-color: #282c34;
 min-height: 100vh;
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 font-size: calc(10px + 2vmin);
 color: white;
}

.App-link {
 color: #61dafb;
}

We can go ahead and start the app up using the development server provided with Create React App. In order to do that, go back to your terminal and type in the command show below.

With Yarn

$ yarn start

With Node/NPM

$ npm start

You should get a message in your terminal that says:

 Local:            http://localhost:3000

Now go ahead and open a browser and put in the address http://localhost:3000. And that should bring up your basic React app.

Now It's Time To Add Some Code πŸ₯³

Let's go back to the App.js file, and start playing around with Framer Motion. FINALLY! πŸŽ‰

Go ahead and add a new line right above the <img /> tag on line 9. Then add a new motion div by add this text <motion.div></motion.div>.
Now put the <img /> tag inside the motion component. The end result should look like the example below:

 <div className="App">
      <main className="App-main">
          <motion.div>
          <img src={logo} className="App-logo" alt="logo" />
        </motion.div>
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </main>
    </div>

Next up we are going to add some Props to our Motion component, so it will actually start moving! πŸš€ The first Prop we are going to add to our motion component is animate={{ rotate: 360, opacity: 1 }}. That is going to tell Framer Motion that we want it to rotate the image 360 degrees (or 1 full turnπŸ‘Œ), and animate the opacity of the image to 1 or 100%. Now we haven't set the initial opacity to anything lower than 1, so that won't really cause much of a change. But we will set them up soon, and the animation will make more sense. Because we have not set up how the animation should play out, it is basically just a quick wiggle at this point. I promise it will get cooler though 😎!

The next set of props we will pass in is going to be transition={{ repeat: Infinity, repeatType: "loop", ease: "easeInOut", duration: 2 }}. Both of these Props are added directly to our <motion.div> component. It should go in like this <motion.div ...props > <img /> </motion.div>. The props are going to define how we would like our animation to play out 🎭. The first object value we pass in is repeat: with a value of Infinity. This means we want our animation to continue for Infinity (which means foreverπŸ˜…).

Next we pass in repeatType: with a value of "mirror". This dictates how we would like the animation to start back up once it has ended. Next up we set the type of ease, or timing function, that we would like our animation to have. If you want more info on animation easing, check out this page on MDN Web Docs. We set the animation timing to easeInOut. This will make it slower at the beginning and the end of the animation. The final key/value pair we pass in via Props is duration: 2. That means we would like the animation to play out over 2 seconds. After finishing this tutorial, you may want to go back and play around with these Props. And see what cool animations you can come up with yourself. πŸ’―

So far our App.js file should look like this:

<div className="App">
      <main className="App-main">
          <motion.div
           animate={{ rotate: 360, opacity: 1 }}
           transition={{
            repeat: Infinity,
            repeatType: "mirror",
            ease: "easeInOut",
            duration: 2
            }}>
          <img src={logo} className="App-logo" alt="logo" />
        </motion.div>
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </main>
    </div>

Next, we are going to add a transition from 0.25 opacity to full opacity or 1. Or as I like to call it, "a sweet fade-in, fade-out thing 😎". Wait did this guy just put an emoji in quotes?? Yes.

Okay back to the whole fade-in transition thing, in order to achieve that we are just going to add another Prop to our <motion.div> component. Underneath the transition Prop type in initial={{ opacity: 0.25 }}. And that's it!

Wow that Framer Motion sure is great! Now the App.js file should look like this:

<div className="App">
      <main className="App-main">
          <motion.div
           animate={{ rotate: 360, opacity: 1 }}
           transition={{
            repeat: Infinity,
            repeatType: "mirror",
            ease: "easeInOut",
            duration: 2
            }}
           initial={{ opacity: 0.25 }}
          >
          <img src={logo} className="App-logo" alt="logo" />
        </motion.div>
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </main>
    </div>

Next up we're going to add some effects to the logo when we hover over it, or when we tap on it. While the hover feature will only work with a mouse, the tap feature will work with both a mouse and a finger on mobile devices.

For the whileHover prop we are going to set it to scale up the icon 125%. We do that by adding the prop whileHover={{ scale: 1.25 }} to the <motion.div>.

Next, we add the whileTap prop to the <motion.div> and set it to scale down to half the size, and set a duration of half a second. We will add the whileTap={{ scale: 0.5, duration: 0.5 }} to the <motion.div>.

Now if you look at your browser window and use your mouse, you can see that hovering over the logo makes the logo bigger. While clicking and holding the mouse button will shrink the logo.

Let's go ahead and add a prop of drag to our <motion.div> component. This will allow the user to drag the logo around to whatever part of the screen they want.

Pretty cool, huh? 🀩

Now we just have one more part of the project to do, and we will be finished. The final piece that we need is to set some boundaries for our logo so when we click and toss the logo it stays inside the bounds of what is visible.

The Final Piece

As mentioned above, the final piece of this project will be to contain our React logo and make it stay inside the visible space in the browser. Since we are using React to control our state and our animations, we will use React to contain the logo.

In order to do this, we are going to bring in useRef from React. We will do so by adding it to the first line as a named import.

import React, { useRef } from "react";

Next inside of our App component we are going to declare a variable to hold the reference for us, and set its initial value to null. We declare this variable on the first line after function App(){ but before our return statement. You can name your variable whatever you like, I named mine constraintsRef.

Here is what it should look like:

const constraintsRef = useRef(null);

Now we are going to pass that ref to our <main > element using the components ref prop to hold the value of the constraints. Your <main > component should now look like this.

<main className="App-main" ref={constraintsRef}>

Finally, we need to pass our ref to our <motion.div > as well. And we need to set it to be a constraint or boundary for our component. Luckily Framer Motion has a special prop for that called dragConstraints. For the value of dragConstraints we are going to pass in our ref. In this case I named it constraintsRef, so that will be the variable I will pass in.

The finished code for App.js should now look like the code shown below.

import React, { useRef } from "react";
import { motion } from "framer-motion";
import logo from "./logo.svg";
import "./App.css";

function App() {
 const constraintsRef = useRef(null);

 return (
  <div className="App">
   <main className="App-main" ref={constraintsRef}>
    <motion.div
         animate={{ rotate: 360, opacity: 1 }}
         transition={{
            repeat: Infinity,
            repeatType: "mirror",
            ease: "easeInOut",
            duration: 2
            }}
       initial={{ opacity: 0.25 }}
       whileHover={{ scale: 1.25 }}
       whileTap={{ scale: 0.5, duration: 0.5 }}
       drag
       dragConstraints={constraintsRef}
 >
   <img src={logo} className="App-logo" alt="logo" />
 </motion.div>

 <p>
   Feel free to check out the source code, I hope you all had lots of
   fun!{" "}
   <span role="img" aria-label="a party popper shooting ribbons">
   πŸŽ‰
   </span>
 </p>

 <a
     className="App-link"
     href="https://github.com/petercr/motion-time"
     target="_blank"
     rel="noopener noreferrer" >
     Source Code on GitHub
  </a>

  </main>
 </div>
 );
}

Here is a CodeSandbox with the finished app running, and all of the code from this post:

I hope that you enjoyed this post, a little history about animations, and learning about Framer Motion.

Please feel free to leave links to your own cool Framer Motion projects in the comments πŸ‘‹πŸ» or if you made any cool changes to the base project.

I am a Front-End developer and designer, who is usually playing around with React or Svelte when I'm not designing a retro ice cream truck. πŸ¦πŸš› I am available for hire, contact me if you have any questions.

Feel free to reach out to me on Twitter at @PeteCapeCod.