How to create Scroll-Linked Animations with React and Framer Motion 💃🏻🕺🏻
Tags: react, typescript, framermotion
Published: 2023-03-02 14:48:26

---
title: How to create Scroll-Linked Animations with React and Framer Motion 💃🏻🕺🏻
published: true
date: 2023-03-01 13:15:06 UTC
tags: react, typescript, framermotion
canonical_url: https://mintel.me/how-to-create-scroll-linked-animations-with-react-and-framer-motion/
---

 ![How to create Scroll-Linked Animations with React and Framer Motion 💃🏻🕺🏻](https://mintel.me/content/images/2023/03/Untitled-design--1-.png)

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.

<iframe width="1000" height="500" src="https://codesandbox.io/embed/scroll-reveal-m8ubub" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

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](https://www.typescriptlang.org/docs/handbook/intro.html?ref=mintel-me). If you want to learn more about Framer Motion, check out the [official documentation](https://www.framer.com/motion/?ref=mintel-me). Happy animating!