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
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:
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:
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:
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
: CreatesAnimationControls
, 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>
);
}
- The
className
anddelay
properties was added to provide aclassName
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. - The
ref
attribute combined with theuseInview
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. - 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 auseEffect
.
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:
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