React scroll animations with Framer Motion
Tags: react
Published: 2022-05-11 17:56:29

**Written by [David Omotayo](https://blog.logrocket.com/author/davidomotayo/)✏️**

Animations can provide a powerful user experience if they are well executed. However, attempting to create stunning animations with CSS can be nerve-wracking. Many animation libraries promise to simplify the process, but most simply aren’t comprehensive enough for building complex animations.

In this article, we’ll demonstrate how to create scroll animations with [Framer Motion](https://www.framer.com/motion/), a complete animation library that doesn't require you to be a CSS expert to create beautiful animations.

_Jump ahead:_

*   [Prerequisites](#prerequisites)
*   [What is Framer Motion?](#what-is-framer-motion)
*   [What is intersection observer functionality?](#what-is-intersection-observer-functionality)
*   [Getting started](#getting-started)
*   [Creating the demo app](#creating-the-demo-app)
*   [Animating with variants](#animating-with-variants)
*   [Adding scroll reveal functionality](#adding-scroll-reveal-functionality)

## Prerequisites

*   Working knowledge of React and its concepts, including Hooks
*   Working knowledge of CSS properties such as opacity, transition, and scale

There’s no need to have any prior knowledge of Framer Motion. This article will introduce the library’s basic concepts and build on those in the demonstration portion.

Let’s start with a little background on Framer Motion and intersection observer functionality.

## What is Framer Motion?

Framer Motion is an animation library for creating declarative animations in React. It provides production-ready animations and a low-level API to help simplify the process of integrating animations into an application.

Some React animations libraries, like [react-transition-group](https://github.com/reactjs/react-transition-group) and [transition-hook](https://github.com/iamyoki/transition-hook), [animate elements with manually configured CSS transitions](https://blog.logrocket.com/how-build-faster-animation-transitions-react/). Framer Motion takes a different approach, by animating elements under the hood with preconfigured styles.

`motion` and `uaeAnimation` are two styles that are triggered and controlled by functions exposed by Framer Motion. The `motion` function is used to create motion components, and these are the building blocks of Framer Motion.

By prefixing `motion` to a regular HTML or SVG element, the element automatically becomes a motion component:

<pre><motion.h1>Motion Component</motion.h1>
</pre>

A motion component has access to several props, including the `animate` prop. `animate` takes in an object with the defined properties of the components to be animated. The properties defined in the object are animated when the component mounts.

## What is intersection observer functionality?

Framer Motion animates elements when they mount on the DOM. It doesn't have inbuilt functionality for animating elements based on their scroll position on the viewport. To address this, we’ll need to implement an intersection observer functionality that will prevent an element from mounting until its scroll position is in the viewport.

We can build this functionality from scratch as a custom Hook using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). This JavaScript API provides a way to asynchronously observe changes in the intersection of a target element with a top-level document viewport.

According to the [documentation](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), this API registers a callback function that is executed whenever an element we want to monitor enters or exits another element or enters or exits the viewport.

Alternatively, we can use a library that is designed to handle this functionality. This is the approach that we’ll follow in this article. We’ll be using the [react-intersection-observer](https://github.com/thebuilder/react-intersection-observer) library, which is a React implementation of the intersection observer API. This library provides Hooks and render props that make it easy to track the scroll position of elements on the viewport.

react-intersection-observer is a relatively small package, so there’s no need to worry about the overhead it may add to your project.

![Bundle size of react-intersection-observer](https://blog.logrocket.com/wp-content/uploads/2022/05/bundle-size-react-intersection-observer.png) Source: [bundlephobia](https://bundlephobia.com/package/framer-motion@6.3.2).

Now, let's set up a simple React project and install the necessary dependencies.

## Getting started

We’ll start by installing React:

```bash
npx create-react-app my-app
```

Next, we’ll install Framer Motion and `react-intersection-observer`:

```bash
npm i react-intersection-observer framer-motion
```

Next, we'll set up a demo app and will use Framer Motion and the react-intersection-observer library to identify when the elements are in view and then apply an animation.

## Creating the demo app

First, we’ll create a box component (this could be a or card, modal, or anything else) and import it into the main component, `App.js` . We’ll animate this main component when it enters the viewport.

```js
/*Box component*/
const Box = () => {
  return (
    <div className="box">
      <h1>Box</h1>
    </div>
  );
};

/*Main component*/
export default function App() {
  return (
    <div className="App">
      <Box /> /*imported Box component*/ /*imported Box component*/
    </div>
  );
}
```

Next, we’ll import everything else that’s required to create animation from the libraries we installed earlier:

*   `motion` and `useAnimation` Hooks from Framer Motion
*   `useEffect` Hook from React
*   `useInView` Hook from react-intersection-observer

```js
import { motion, useAnimation } from "framer-motion";

    import { useInView } from "react-intersection-observer";

    import { useEffect } from "react";
```

These are the essential Hooks we’ll need to animate our box component. You’ll get an idea of how each Hook works a little later in this tutorial.

Inside our component is a `div` element with the `className`: `box`. In order to animate the `box` element, we need to make it a motion component.

We do this by prefixing `motion` to the element:

```js
const Box = () => {
  return (
    <motion.div className="box">
      <h1>Box</h1>
    </motion.div>
  );
};
```

We can start animating the `box` element as is, by simply adding an `initial` and `animate` prop to the `motion` component and directly defining their object values.

```js
<motion.div 
animate={{ x: 100 }} 
initial={{x: 0}} 
className="box"
></motion.div>
```

For more complex animation, Framer Motion offers a variants feature.

## Animating with variants

Variants are a set of predefined objects that let us declaratively define how we want the animation to look. Variants have labels that can be referenced in a motion component to create animations.

Here’s an example of a variant object:

```js
const exampleVariant = {
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
}
```

Inside this variant object, `exampleVariant`, are two properties: `visible` and `hidden`. Both properties are passed an object as the value. When the element is `visible`, we want the `opacity` to be `1`; when it is `hidden`, we want it to be `0`.

The above variant object can be referenced in a motion component, like so:

```js
<motion.div variants={exampleVariant} />
```

Next, we’ll create a variant and pass it as a prop to our motion component:

```js
const boxVariant = {
  visible: { opacity: 1, scale: 2 },
  hidden: { opacity: 0, scale: 0 },
}
```

In this variant object, `boxVariant`, we included a `scale` property so that the element will scale up in size when it is `visible` and scale down when it is `hidden`.

To reference this variant object it in our motion component, we’ll add a `variants` prop to the motion component and pass it the variant's label:

```js
<motion.div
  variants={boxVariant}
  className="box"
/>
```

Right now, nothing is happening to our motion component; it has access to the variant object, but it doesn't know what to do with it. The motion component needs a way to know when to start and end the animations defined in the variant object.

For this, we pass the `initial` and `animate` prop to the motion component:

```js
<motion.div
  variants={boxVariant}
  className="box"
  initial="..."
  animate="..."
/>
```

In the above code, the `initial` prop defines the behavior of a motion component before it mounts, while the `animate` prop is used to define the behavior when it mounts.

Now, we’ll add a fade-in animation effect to the motion component by setting the `opacity` of the component to `0` before it mounts and back to `1` when it mounts. The `transition` property has a duration value that indicates the animation `duration`.

```js
<motion.div
  className="box"
  initial={{ opacity: 0, transition:{duration: 1}}}
  animate={{opacity: 1}}
/>
```

Since we're using variants, we don't have to explicitly set the values of the `initial` and `animate` properties.

Instead, we can dynamically set them by referencing the `hidden` and `visible` properties in the variant object we created earlier:

```js
const boxVariant = {
  visible: { opacity: 1, scale: 2 },
  hidden: { opacity: 0, scale: 0 },
}
...
<motion.div
  variants={boxVariant}
  initial="hidden"
  animate="visible"
  className="box"
/>
```

The motion component will inherit the values of the variant object’s `hidden` and `visible` properties and animate accordingly:

![Two Boxes Animate at Once](https://blog.logrocket.com/wp-content/uploads/2022/05/two-boxes-animate-at-once.gif)

Now that we have a working animation for our motion component, the next step is to use the react-intersection-observer library to access the Intersection Observer API and trigger the animation when the component is in view.

## Adding scroll reveal animation with `useInView` and `useAnimation` Hooks

Framer Motion animates elements when they mount, so before we can animate elements based on their scroll position, we need to be able to control when they mount and unmount.

The `useAnimation` Hook provides helper methods that let us control the sequence in which our animations occur. For example, we can use the `control.start` and `control.stop` methods to manually start and stop our animations.

`useInView` is a react-intersection-observer Hook that lets us track when a component is visible in the viewport. This Hook gives us access to a `ref`, that we can pass into the components we want to watch, and the `inView` Boolean, which tells us whether a component is in the viewport.

For example, if we pass `ref` to a component as a prop and log `inView` to the console, the console will display `true` when the component is scrolled into the viewport and `false` when it leaves the viewport.

![False and True Outputs](https://blog.logrocket.com/wp-content/uploads/2022/05/false-true-outputs.gif)

Now, we’ll use the `useAnimation` Hook to trigger animations on our motion component with when it enters the viewport.

First, we’ll destructure `ref` and `inView` from the `useInView` Hook, and assign `useAnimation` to a variable:

```js
const control = useAnimation()
const [ref, inView] = useInView()
```

Next, we’ll add `ref` to our motion component as a prop and pass the `control` variable as a value to the `animate` prop:

```js
<motion.div
  ref={ref}
  variants={boxVariant}
  initial="hidden"
  animate={control}
  className="box"
/>
```

Finally, we’ll create a `useEffect` to call the `control.start` method whenever the component we're watching is in view, and pass the `control` and `inView` variables as the dependencies:

```js
useEffect(() => {
    if (inView) {
      control.start("visible");
    } 
  }, [control, inView]);
```

Inside the `useEffect` callback function, we perform a conditional check with an `if` statement to check if the motion component is in view. If the condition is `true`, `useEffect` will call the `control.start` method with a `"visible"` value passed into it. This will trigger the `animate` property on our motion component and start the animation.

Now, if we scroll up and down our viewport, the box components will animate when their scroll position enters the viewport:

![Boxes Animate While Scrolling](https://blog.logrocket.com/wp-content/uploads/2022/05/boxes-animate-scrolling.gif)

Notice how the box components only animate the first time they enter the viewport. We can make them animate every time they are in view by adding an `else` block to the `if` statement in the `useEffect` callback function, and calling the `control.start` method, but with a `"hidden"` value passed into it this time.

```js
else {
      control.start("hidden");
    }
```

Now, if we scroll up and down our viewport, the box components will animate each time their scroll position enters the viewport:

![Boxes Animate Into Viewport](https://blog.logrocket.com/wp-content/uploads/2022/05/boxes-animate-into-viewport.gif)

Here’s a look at the final code for creating scroll animations with Framer Motion:

```js
import { motion, useAnimation } from "framer-motion";
import { useInView } from "react-intersection-observer";
import { useEffect } from "react";

const boxVariant = {
  visible: { opacity: 1, scale: 1, transition: { duration: 0.5 } },
  hidden: { opacity: 0, scale: 0 }
};

const Box = ({ num }) => {

  const control = useAnimation();
  const [ref, inView] = useInView();

  useEffect(() => {
    if (inView) {
      control.start("visible");
    } else {
      control.start("hidden");
    }
  }, [control, inView]);

  return (
    <motion.div
      className="box"
      ref={ref}
      variants={boxVariant}
      initial="hidden"
      animate={control}
    >
      <h1>Box {num} </h1>
    </motion.div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Box num={1} />
      <Box num={2} />
      <Box num={3} />
    </div>
  );
}
```

## Conclusion

In this article, we introduced the basics of the Framer Motion animation library and demonstrated how to use it to create scroll animations. We discussed how to control animations using the `useAnimation` Hook, and how to trigger animations with Intersection Observer API (accessed through the react-intersection-observer library).

This article offers just a glimpse into the extensive range of animations that can be created with Framer Motion. Visit the [official docs](https://www.framer.com/docs/animation/) and see what else you can come up with.

---

## Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket](https://www2.logrocket.com/react-performance-monitoring). 

![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free](https://www2.logrocket.com/react-performance-monitoring).