react-x-motion

Animated Text Tutorial | React & Motion

React & Motion | Tutorial

Simon Bremner
By Simon Bremner
March 14, 2025
Creating animated sections in your website has never been easier with React & Motion

The Goal

Every now and then I have the urge to make something fun. I enjoy a smooth user-experience and animation libraries are making it easier every year to implement fun experiences that are supported by almost all modern browsers.

For simonbremner.net, I decided to add Motion as my animation library. It's not the most lightweight or the most customizable, but the setup and the development cost is minimal as long as you're happy with their pre-built animations.

For this example, I want to create the effect seen here: Split text | Motion examples with a couple of modifications. I want the text to animate in based on my scroll position and I want it to animate out when I scroll back up the page.

Additionally, I prefer TypeScript over Vanilla JS, so everything in this post will be in TSX format. If you'd like to use JSX or even Vanilla JS without react, there are tutorials on: Docs - Motion. They're basically the same...ish.

Example Animation

The Setup

I'm not going to go through the process of setting up a React Application. If you're reading this, you should already have created a React Application or have React imported into your project. Obviously, meta frameworks like Next and Remix are also viable. For this example, I'm using Astro as my framework because I think it's extra shiny right now, but you won't see any Astro-specific code here.

Anyways, depending on your favourite package manager, here are your installation commands:

bash
// pnpm
pnpm install motion

// npm
npm install motion

// yarn
yarn add motion

Then, import the necessary packages into your component. For this project, we are only going to be using motion, useScroll, and useTransform from Motion's library:

typescript
import { motion, useScroll, useTransform } from "motion/react"
import { useRef } from "react"

The Main Component

This is where we'll define the main AnimatedText.tsx component. It's pretty straightforward - it receives the content: string prop and passes it, along with some animation details, to the AnimatedWord.tsx component that we will create next.

I forgot to mention that I'm using TailwindCSS here. That's why I'm using flex & flex-wrap as my className

typescript
const AnimatedText = ({ text }: {
    text: string,
}) => {

    // Create sectionRef for scroll tracking
    const sectionRef = useRef<HTMLDivElement>(null);

    // Track scroll progress of sectionRef in the browser's viewport
    const { scrollYProgress } = useScroll({
        target: sectionRef,
        offset: ["start end", "center center"],
    });

    // Split text into individual words
    const words = text.split(' ').filter(word => word.length > 0);

    return (
        <motion.div ref={sectionRef}>
            <div className="flex flex-wrap">
                {words.map((word, wordIndex) => (
                    <AnimatedWord
                        key={`word-${wordIndex}`}
                        word={word}
                        wordIndex={wordIndex}
                        totalWords={words.length}
                        scrollYProgress={scrollYProgress}
                    />
                ))}
            </div>
        </motion.div>
    );
};

The details:

  1. sectionRef defines the element we want to target in the DOM
  2. useScroll tracks the position of the target element within the browser's viewport
  3. target: sectionRef tells motion what element to track
  4. offset: ["start end", "center center"] goes like this:
    1. start is the vertical location of target
    2. end is the vertical location of the viewport
    3. The two arguments in the array define where the animation will start and end respectively
  5. words splits each word in the string and passes them into the WordAnimation.tsx component through a map function

Animating Individual Words

Now that we are passing each word to this component as well as the scroll position of the section as a whole, we can do some fun things like stagger each word to animate based on its position in the array of words.

typescript
const WordAnimation = ({
    word,
    wordIndex,
    totalWords,
    scrollYProgress,
}: {
    word: string,
    wordIndex: number,
    totalWords: number,
    scrollYProgress: MotionValue<number>,
}) => {

    // Calculations to set when this word should appear
    const wordStart = (wordIndex / totalWords) * 0.75;
    const wordEnd = wordStart + 0.05;

    // Calculate the opacity and y position of each word
    const opacity = useTransform(
        scrollYProgress,
        [wordStart, wordEnd],
        [0, 1]
    );
    const y = useTransform(
        scrollYProgress,
        [wordStart, wordEnd],
        [20, 0]
    );

    return (
        <motion.div
            style={{
                opacity,
                y,
                marginRight: "0.4em",
                marginBottom: "0.1em",
                display: "inline-block",
            }}
            className="text-foreground font-normal"
        >
            {word}
        </motion.div>
    );
};

The details:

  1. wordStart and wordEnd are pretty self-explanatory. I'm just calculating when to start and end each animation. Play with these numbers to see what suits you
  2. The third argument in useTransform is providing the expected values at each calculated point ([0, 1] just means opacity: 0 and opacity: 1 when passed into style of motion.div

Using the Component

To use our component, all we have to do is pass a string of words into it. It may look pretty basic right now, but you can pass your own text styles, spacing, etc. to make it your own!

typescript
<AnimatedText 
  text="Peter Piper doesn't like peppers."
/>

Wrapping Up

And that's it! A scrolling text animation component that's lightweight, customizable, and adds some serious polish to your site.

Here are some extra things to try:

  • Section Size: Since we're animating based on the position of the section in the viewport, be careful with long content. For larger sections of text, it would be worth mapping each paragraph in separate components.
  • Highlighting: Highlighting individual words is useful for drawing attention to important content. You could pass an array of word indexes to highlight individual words. Then create a ternary operator that applies styles to each word if it's in the array.
  • Customization: Play with the timing, positioning, or add new animations. I used opacity and y-position, but you could have each word rotate slightly, scale up/down, or even have them come from off the screen. Enjoy the creative process!

The most important thing is to have fun with it!

Frameworks & Packages
React
React
Motion
Motion
Typescript
Typescript

Tags

TutorialReactMotionTypescriptAnimationTailwindCSS