Scroll Stagger Text

An animated component that reveals text on scroll.

This is some visible content. Scroll down to reveal some more text content 

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Tenetur magni. 

Usage

To use the StaggerText component, follow these steps:

  1. Installation: Install the necessary package using npm, yarn or pnpm.
npm i framer-motion clsx tailwind-merge
  1. Tailwind CSS: Install tailwind css or you can customize the styling according to your ui library

  2. Add util file: lib/utils.ts

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
  1. Source code: Copy & paste the below code into component/ui/motion or according to your code structure
"use client";
import { cn } from "@/lib/utils";
import {
  motion,
  MotionValue,
  useScroll,
  useSpring,
  useTransform,
  type UseScrollOptions,
} from "framer-motion";
import { PropsWithChildren, useEffect, useRef } from "react";

export interface ScrollStaggerText extends PropsWithChildren {
  className?: string;
  container?: UseScrollOptions["container"];
}

export function ScrollStaggerText({
  className,
  children,
}: ScrollStaggerText) {
  const paragraphRef = useRef<HTMLParagraphElement>(null);

  const { scrollYProgress } = useScroll({
    target: paragraphRef,
    offset: ["start end", "center center"],
  });

  useEffect(
    () => scrollYProgress.on("change", (v) => console.log(v)),
    [scrollYProgress]
  );

  if (typeof children !== "string") {
    throw Error("ScrollStaggerText only accept children of type string");
  }

  const words = children.split(" ");

  return (
    <p
      ref={paragraphRef}
      className={cn(
        "flex flex-wrap text-3xl sm:text-4xl md:text-5xl font-bold",
        className
      )}
    >
      {words.map((word, i) => (
        <Word
          key={word + i}
          scrollYProgress={scrollYProgress}
          range={[i / words.length, (i + 1) / words.length]}
        >
          {word}&nbsp;
        </Word>
      ))}
    </p>
  );
}

interface WordProps extends PropsWithChildren {
  range: [number, number];
  scrollYProgress: MotionValue<number>;
}

function Word({ children, range, scrollYProgress }: WordProps) {
  const motionY = useTransform(scrollYProgress, range, [50, 0]);

  const y = useSpring(motionY, { stiffness: 600, damping: 80 });

  return (
    <span className="overflow-hidden">
      <motion.span style={{ y }} className="block">
        {children}
      </motion.span>
    </span>
  );
}