How to create Scroll-Linked Animations with React and Framer Motion ππ»πΊπ»
Marc Mintel
Posted on March 2, 2023
Scroll-linked animations are a popular technique that links animation to the user's scrolling on the page. As you scroll down the page, different elements will animate in or out, giving your website a unique and dynamic feel.
In this tutorial, we'll be using React and Framer Motion to create a scroll-linked animation. Framer Motion is a powerful animation library that makes it easy to create fluid and beautiful animations with React. We'll also be using TypeScript, a popular language that adds strong typing to JavaScript and can help catch errors before runtime.
The Code
Here's the TypeScript code we'll be working with:
import { motion, useScroll, useTransform } from 'framer-motion'
import { useElementViewportPosition } from '../hooks/useElementViewportPosition'
import React, { useRef, PropsWithChildren } from 'react'
export const ScrollReveal: React.FC<PropsWithChildren<{
offset?: number
distance?: number
}>> = ({ offset = 0, distance = 50, children }) => {
const ref = useRef(null)
const { position } = useElementViewportPosition(ref, offset)
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, position, [0, 1])
const y = useTransform(scrollYProgress, position, [distance, 0])
return (
<motion.div
ref={ref}
style={{
opacity,
y,
}}
>
{children}
</motion.div>
)
}
And here's the code for the useElementViewportPosition
hook that we'll be using:
import { useState, useEffect, RefObject } from 'react'
export function useElementViewportPosition(
ref: RefObject<HTMLElement>,
offset = 0,
) {
const [position, setPosition] = useState([0, 0])
useEffect(() => {
const update = () => {
if (!ref || !ref.current) return
const pageHeight = document.body.scrollHeight
const start = ref.current.offsetTop
const end = start + ref.current.offsetHeight
setPosition([(start + offset) / pageHeight, (end + offset) / pageHeight])
}
update()
document.addEventListener('resize', update)
return () => {
document.removeEventListener('resize', update)
}
}, [offset, ref])
return { position }
}
How It Works
So, how do these two pieces of code work together to create those scroll-linked animations? Let me break it down for you.
First, we're importing motion
, useScroll
, and useTransform
from Framer Motion, and useElementViewportPosition
from our own custom hook. We're also importing some stuff from React.
import { motion, useScroll, useTransform } from 'framer-motion'
import { useElementViewportPosition } from '../hooks/useElementViewportPosition'
import React, { useRef, PropsWithChildren } from 'react'
Next, we're defining a new component called ScrollReveal
. This component takes two optional props: offset
and distance
, which we'll explain in a bit. It also takes some children that we'll animate as the user scrolls.
export const ScrollReveal: React.FC<PropsWithChildren<{
offset?: number
distance?: number
}>> = ({ offset = 0, distance = 50, children }) => {
Inside this component, we're creating a new ref with useRef(null)
. We're also calling our useElementViewportPosition
hook and passing in our ref
and offset
. This hook will return a value called position
.
Breaking Down the Code
Now that we've seen the code in action, let's break it down line by line.
import { motion, useScroll, useTransform } from 'framer-motion'
import { useElementViewportPosition } from '../hooks/useElementViewportPosition'
import React, { useRef, PropsWithChildren } from 'react'
Here we have our imports. We're using Framer Motion, a popular React animation library, and importing motion
, useScroll
, and useTransform
. We're also importing a custom hook called useElementViewportPosition
from our ../hooks
folder. Finally, we're importing React and a couple of types.
export const ScrollReveal: React.FC<PropsWithChildren<{
offset?: number
distance?: number
}>> = ({ offset = 0, distance = 50, children }) => {
This line exports a React functional component called ScrollReveal
. It's defined as a function that takes a prop object with optional offset
and distance
properties, as well as the children
prop. The component will use these props to determine how much to reveal and how far to animate.
const ref = useRef(null)
This line creates a React ref called ref
that we'll use later to track the position of the component in the viewport.
const { position } = useElementViewportPosition(ref, offset)
This line calls the useElementViewportPosition
hook we imported earlier, passing in our ref
and offset
props. The hook returns an object with a position
property that we'll use to animate the component.
const { scrollYProgress } = useScroll()
This line calls another Framer Motion hook called useScroll
, which returns an object with a scrollYProgress
property that we'll use to animate the component.
useTransform does all the Magic!
The useTransform
hook is part of the Framer Motion library, which is used in the ScrollReveal
component.
The useTransform
hook is used to create a relationship between the scroll position and an animated property of an element. In this case, it's used to adjust the opacity and y-position of the animated element (motion.div
) as the user scrolls.
In more detail, useTransform
takes three arguments: input
, inputRange
, and outputRange
. The input
argument is a value that changes over time, in this case the scrollYProgress
from useScroll
. The inputRange
argument is an array that defines the range of the input value (e.g. [0, 1]
), and the outputRange
argument is an array that defines the range of values for the output (e.g. [distance, 0]
).
const opacity = useTransform(scrollYProgress, position, [0, 1])
const y = useTransform(scrollYProgress, position, [distance, 0])
These lines use the useTransform
hook to create two new variables, opacity
and y
, that we'll use to animate the component. The useTransform
hook takes three arguments: a value to transform (in this case scrollYProgress
), the range of the input values (position
), and the range of the output values ([0, 1]
for opacity
and [distance, 0]
for y
).
return (
<motion.div
ref={ref}
style={{
opacity,
y,
}}
>
{children}
</motion.div>
)
Finally, we return a motion.div
with our ref
and two style properties, opacity
and y
, that we created using the useTransform
hook. We also render the children
prop inside the motion.div
.
Conclusion
In this article, we've explored how to use TypeScript, React, and Framer Motion to create a scroll-linked animation. We've broken down the code line by line and explained what each part does. If you want to learn more about TypeScript, check out the official documentation. If you want to learn more about Framer Motion, check out the official documentation. Happy animating!
Posted on March 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.