Back to Dictionary
Scroll & Navigation
●●○Customize
Context dependent

Text Reveal Pipeline

Large typographic paragraphs where the text color or opacity transitions from faded to solid exactly mapped to the user's scroll position.

Use

Making a bold, philosophical statement on a landing page

Avoid

Body paragraphs or functional reading

Safer Alternative

No safer default listed.

Risk Level

Low risk

Live Demo

Scroll down
Animationbreatheslifeintostaticinterfaces,transformingclicksintoexperiences.

What It Is

Large typographic paragraphs where the text color or opacity transitions from faded to solid exactly mapped to the user's scroll position.

When to Use

  • Making a bold, philosophical statement on a landing page
  • Pacing the reader through a key value proposition

When NOT to Use

  • Body paragraphs or functional reading

Configuration Tips

  • 01Map scrollYProgress to an array of word indices to turn them solid one by one as the user scrolls

You've Seen It In

LinearChronicleApple

AI Implementation Prompts

Move from a fast scaffold to production details, then tune timing and edge states.

Make this paragraph of text fade in word-by-word tied to the user's scroll position.

Reference Implementation

The same implementation approach used by the live preview, kept compact enough to inspect and adapt.

"use client";
import { useRef, useEffect } from 'react';
import { motion, useScroll, useTransform } from 'framer-motion';

export function TextRevealPipelineDemo({ isPlaying = false }: { isPlaying?: boolean }) {
    const containerRef = useRef<HTMLDivElement>(null);
    const textRef = useRef<HTMLDivElement>(null);

    const { scrollYProgress } = useScroll({
        container: containerRef,
        target: textRef,
        offset: ["start 80%", "end 20%"]
    });

    useEffect(() => {
        if (!isPlaying || !containerRef.current) return;
        let pos = 0;
        let direction = 1;

        const interval = setInterval(() => {
            if (!containerRef.current) return;
            pos += 2 * direction;
            if (pos > 500) direction = -1;
            if (pos <= 0) direction = 1;
            containerRef.current.scrollTop = pos;
        }, 16);

        return () => clearInterval(interval);
    }, [isPlaying]);

    const phrase = "Animation breathes life into static interfaces, transforming clicks into experiences.";
    const words = phrase.split(" ");

    return (
        <div className="flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl bg-white p-4 shadow-sm ring-1 ring-stone-200">
            <div
                ref={containerRef}
                className="w-full max-w-sm h-56 overflow-y-scroll rounded-xl shadow-inner bg-stone-900 hide-scrollbar"
                style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
            >
                <div className="h-[200px] flex items-center justify-center text-stone-500 font-medium text-xs tracking-widest uppercase">Scroll down</div>

                <div ref={textRef} className="px-8 py-12 flex flex-wrap gap-x-2 gap-y-1 justify-center">
                    {words.map((word, i) => {
                        const start = i / words.length;
                        const end = start + (1 / words.length);
                        "text-stone-400 italic">// eslint-disable-next-line react-hooks/rules-of-hooks
                        const opacity = useTransform(scrollYProgress, [start, end], [0.15, 1]);
                        return (
                            <motion.span
                                key={i}
                                style={{ opacity }}
                                className="text-[22px] font-bold text-white tracking-tight"
                            >
                                {word}
                            </motion.span>
                        )
                    })}
                </div>

                <div className="h-[200px]" />
            </div>
        </div>
    );
}