logo
cd ..

Scroll Reveal Animation in React using Framer Motion

Comments
6 min

Scroll Reveal Animation in React using Framer Motion

Discover the art of crafting captivating scroll reveal animations in your Reactjs application using the powerful, intuitive React Framer Motion library

framer motion cover image

I recently added a few animations to my personal site to improve its aesthetics and the result I think was excellent. You can refresh this page to see the animation in action. The animations were added using the React Framer motion library.

Overview

Framer Motion is a simple yet powerful motion library for React that provides a wide range of tools and components to easily create fluid and visually appealing animations in your React application. With over 2 million weekly installs on npm, it is easily one of the most popular animation libraries available.

In this guide, I'll show you how to utilize framer motion to implement a scroll reveal animation in your Reactjs application. I've made the steps very easy to follow so you can customize the animation or build your own from scratch. You can view the source code for this guide or check out the implementation on my blog.

Step 1. Install Framer Motion

Assuming you already have your react application running (if not, quickly run npm create vite@latest my-app -- --template react-ts), install framer motion using the command below:

terminal

npm install framer-motion

Step 2. Setup Markup

To illustrate the animation, I've created a few section elements wrapped in a main tag and added some styling to it. Here's the markup:

app.tsx

import "./App.css";

function App() {
  return (
    <main>
      <section id="section1">
        <h1>Section One</h1>
        <p>Hey! I am the first section</p>
        <a href="#section2">Scroll to 2</a>
      </section>
      <section id="section2">
        <h1>Section Two</h1>
        <p>Hey! I am the second section</p>
        <a href="#section3">Scroll to 3</a>
      </section>
      <section id="section3">
        <h1>Section Three</h1>
        <p>Hey! I am the third section</p>
        <a href="/">Scroll to Top</a>
      </section>
    </main>
  );
}

export default App;

And here's the base styling:

App.css

* {
  background-color: #131615;
  color: #ffffff;
  font-family: "Inter", system-ui, sans-serif;
  margin: 0;
  padding: 0;
}

section {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 1200px;
  margin: 0 auto;
  min-height: 100vh;
  padding: 0 2rem;
}

h1 {
  font-size: 4.5em;
  margin-bottom: 0.5rem;
}

p {
  font-size: 1.2rem;
  max-width: 700px;
  color: #888888;
}

a {
  display: inline-block;
  background-color: #66cf92;
  color: #131615;
  padding: 0.6rem 2rem;
  text-align: center;
  text-decoration: none;
  margin-top: 1.5rem;
}

For the styling, we've given each section a viewport height of 100 so they span out of the viewport. This is just to illustrate how the animation is triggered when the element is within view.

Here's the output:

A large text and small text and button on a dark background
preview of section output

Now we have the markup ready, we can start animating them.

Step 2. Create Motion Component

In other to use the library, you'll first import the motion component from framer-motion into the component you want to animate. You can do this directly in the file created earlier but to make it reusable, create a wrapper src/components/Slide.tsx file and paste the snippets below.

src/components/Slide.tsx

import { motion } from "framer-motion";

type props = {
  children: React.ReactNode;
};

export default function Slide({ children }: props) {
  return (
    <motion.div>
      {children}
    </motion.div>
  );
}

The first step to animating a component with Framer Motion is by first marking the element with a motion. prefix, followed by an element tag name. There's a motion component for every HTML and SVG element, for instance motion.div, motion.article, etc. According to the Framer Motion docs:

Motion components are DOM primitives optimized for 60fps animation and gestures. (Source )

Marking an element with the motion prefix essentially converts it into an advanced element supercharged with animating capabilities and built-in props from the motion library. This allows you to animate the element, add drag gestures and more.

Step 3. Add Motion Properties

The first property we'll add to the element is variants. This takes in an object of predefined targets. With variants, you can set the initial and animated state of the component. Here's an example that adds a slide and fade-in animation to the motion component.

src/components/Slide.tsx

import { motion } from "framer-motion";

type props = {
  children: React.ReactNode;
};

export default function Slide({ children }: props) {
  return (
    <motion.div
      variants={{
        hidden: { opacity: 0, translateX: 90 },
        visible: { opacity: 1, translateX: 0 },
      }}
    >
      {children}
    </motion.div>
  );
}

In the example above, we specified the initial and animated state of the component.

  • The initial state sets the opacity to 0 and translates the element to the left by 90px
  • The animated state resets the opacity back to 1; essentially revealing the elements, and then translates it back to its original position.
Note: The variant targets are simply variables that hold the animation states so you can name them whatever you want.

To trigger the animation, you need to assign the variants to the initial and animate props from framer motion. In the example below, the initial state is set to the hidden variant and the animated state, the visible variant.

src/components/Slide.tsx

import { motion } from "framer-motion";

type props = {
  children: React.ReactNode;
};

export default function Slide({ children }: props) {
  return (
    <motion.div
      variants={{
        hidden: { opacity: 0, translateX: 90 },
        visible: { opacity: 1, translateX: 0 },
      }}
      initial="hidden"
      animate="visible"
    >
      {children}
    </motion.div>
  );
}

Now you can wrap any element with this component to animate it.

src/App.tsx

import "./App.css";
import Slide from "./components/Slide";

function App() {
  return (
    <main>
      <section id="section1">
        <Slide>
          <h1>Section One</h1>
          <p>Hey! I am the first section</p>
          <a href="#section2">Scroll to 2</a>
        </Slide>
      </section>
      <section id="section2">
        <Slide>
          <h1>Section Two</h1>
          <p>Hey! I am the second section</p>
          <a href="#section3">Scroll to 3</a>
        </Slide>
      </section>
      <section id="section3">
        <Slide>
          <h1>Section Three</h1>
          <p>Hey! I am the third section</p>
          <a href="/">Scroll to Top</a>
        </Slide>
      </section>
    </main>
  );
}

export default App;

Here's the resulting output when you refresh the page:

Text bouncing on a indigo background
preview of component slide and fade in animation

The motion component also takes in other important properties; transition which allows you to set properties like delay, animation speed, duration and more. Here's an example that produces a bouncing effect.

src/components/Slide.tsx

import { motion } from "framer-motion";

type props = {
  children: React.ReactNode;
};
export default function Slide({ children }: props) {
  return (
    <motion.div
      variants={{
        hidden: { opacity: 0, translateX: 90 },
        visible: { opacity: 1, translateX: 0 },
      }}
      transition={{
        type: "spring",
        duration: 0.2,
        damping: 8,
        delay: 0.1,
        stiffness: 100,
      }}
      initial="hidden"
      animate="visible"
    >
      {children}
    </motion.div>
  );
}

The resulting output:

slide and fade-in animation with bounce effect
slide and fade-in animation with bounce effect

I'm no animation expert, so feel free to play around with this on your own. I suggest you go through the transition section in their docs to see all that is possible with this property.

Step 4. Trigger Animation on Scroll

Although the other elements further down the viewport were wrapped with the <Slide> component, you may have a hard time noticing them animate because the animations are all fired at once. Let's set up a scroll trigger to animate them only when they are visible on the viewport; essentially, when they are scrolled into view.

To do this, we'll import a bunch of hooks both from React and Framer Motion.

src/components/Slide.tsx

import { motion, useInView, useAnimation } from "framer-motion";
import { useRef, useEffect } from "react";
  • useInView: A simple hook that detects when the provided element is within the viewport.
  • useAnimation: Creates AnimationControls, which can be used to manually start, stop and sequence animations on one or more components.

Here's the code to animate the elements when they are scrolled into view. Replace it with your Slide.tsx file and I'll explain what's happening after.

src/components/Slide.tsx

import { motion, useInView, useAnimation } from "framer-motion";
import { useRef, useEffect } from "react";

type props = {
  children: React.ReactNode;
  className?: string;
  delay?: number;
};

export default function Slide({ children, delay, className }: props) {
  const ref = useRef(null);
  const isInview = useInView(ref, { once: true });
  const controls = useAnimation();

  useEffect(() => {
    if (isInview) {
      controls.start("visible");
    }
  }, [isInview]);

  return (
    <motion.div
      ref={ref}
      variants={{
        hidden: { opacity: 0, translateX: 90 },
        visible: { opacity: 1, translateX: 0 },
      }}
      transition={{
        type: "spring",
        duration: 0.2,
        damping: 8,
        delay: delay,
        stiffness: 100,
      }}
      initial="hidden"
      animate={controls}
      className={className}
    >
      {children}
    </motion.div>
  );
}
  1. The className and delay properties was added to provide a className property to the motion component and set a delay prop with a value of delay. Instead of hard-coding the delay value, we passed a prop into the component that can be used to customize the value dynamically.
  2. The ref attribute combined with the useInview hook allows us to know when the element is available in the viewport. It also takes in an optional property that we used to set the animation count to once.
  3. With the useAnimation hook, we set the animation to fire when isInview is true, and to prevent it from firing infinitely, we wrapped it in a useEffect.

To illustrate the delay, we can wrap the button and paragraph text on its own Slide component, then apply a delay to trigger its animation after the heading element's animation has taken place.

Here's the code to achieve this:

src/App.tsx

import "./App.css";
import Slide from "./components/Slide";

function App() {
  return (
    <main>
      <section id="section1">
        <Slide>
          <h1>Section One</h1>
        </Slide>
        <Slide delay={0.3}>
          <p>Hey! I am the first section</p>
          <a href="#section2">Scroll to 2</a>
        </Slide>
      </section>
      <section id="section2">
        <Slide>
          <h1>Section Two</h1>
        </Slide>
        <Slide delay={0.3}>
          <p>Hey! I am the second section</p>
          <a href="#section3">Scroll to 3</a>
        </Slide>
      </section>
      <section id="section3">
        <Slide>
          <h1>Section Three</h1>
        </Slide>
        <Slide delay={0.3}>
          <p>Hey! I am the third section</p>
          <a href="/">Scroll to Top</a>
        </Slide>
      </section>
    </main>
  );
}

export default App;

The final output should look like this:

final preview of slide animation with delay
final preview of slide animation with delay

Conclusion

Animation is one of the known ways of creating a better experience for users. When done right, it transforms boring lifeless sites into aesthetically pleasing experiences. In this post, you've explored one of the most popular React animation libraries, how it works and how to use it to build a scroll-triggered animation.

I trust you've gained insights from this guide. If you found it beneficial, consider sharing it with a friend. Looking forward to engaging with you again in an upcoming post.

Comments

Support

Do you feel like supporting my work? đŸ™‚Buymeacoffee button