Text Scroll Reveal

Each word lights up as the paragraph scrolls through the screen.

Scroll down ↓

Everywordlightsupasyouscroll,turningastaticparagraphintoamomentpeopleactuallyreadfromstarttofinish.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/text-scroll-reveal.tsx

    components/ui/text-scroll-reveal.tsx
    "use client";
    
    import { motion, useScroll, useTransform, type MotionValue } from "motion/react";
    import { useRef, type RefObject } from "react";
    
    function Word({ children, progress, range }: { children: string; progress: MotionValue<number>; range: [number, number] }) {
      const opacity = useTransform(progress, range, [0.15, 1]);
      const y = useTransform(progress, range, [6, 0]);
      return (
        <motion.span style={{ opacity, y }} className="mr-[0.25em] inline-block">
          {children}
        </motion.span>
      );
    }
    
    type TextScrollRevealProps = {
      text: string;
      className?: string;
      /** Track a scrollable element instead of the whole page */
      container?: RefObject<HTMLElement | null>;
    };
    
    export function TextScrollReveal({ text, className = "", container }: TextScrollRevealProps) {
      const ref = useRef<HTMLParagraphElement>(null);
      const { scrollYProgress } = useScroll({ target: ref, container, offset: ["start 0.85", "end 0.45"] });
      const words = text.split(" ");
    
      return (
        <p ref={ref} aria-label={text} className={`flex flex-wrap ${className}`}>
          {words.map((word, i) => {
            const start = i / words.length;
            return (
              <Word key={`${word}-${i}`} progress={scrollYProgress} range={[start, start + 1 / words.length]}>
                {word}
              </Word>
            );
          })}
        </p>
      );
    }
    
    export default function Demo() {
      const ref = useRef<HTMLDivElement>(null);
      return (
        <div ref={ref} className="h-[380px] w-full max-w-2xl overflow-y-auto rounded-2xl border border-zinc-200 bg-white px-8">
          <div className="flex h-56 items-end justify-center pb-6 text-sm text-zinc-400">Scroll down ↓</div>
          <TextScrollReveal
            container={ref}
            text="Every word lights up as you scroll, turning a static paragraph into a moment people actually read from start to finish."
            className="text-3xl font-semibold leading-tight tracking-tight text-zinc-900"
          />
          <div className="h-72" />
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.