Horizontal scrolling text with characters that animate into place as you scroll — perfect for hero sections and immersive storytelling
Drag slider to simulate scroll progress
Copy URL or Code
Paste to your AI coding assistant and customize:
Done. Your AI handles the rest.
Fully customizable. Uses GSAP ScrollTrigger with containerAnimation for nested scroll-triggered effects. Characters scatter from random positions with rotation, scale, and opacity animations.
/*
================================================================================
AI COMPONENT: ScrollTextReveal
================================================================================
WHAT IT DOES:
- Creates a full-viewport horizontal scrolling text section
- Text scrolls from right to left as user scrolls down
- Each character animates from scattered position to final position
- Perfect for hero sections, landing pages, and immersive storytelling
SETUP:
1. Install GSAP: npm install gsap
2. Create file: components/ScrollTextReveal.tsx
3. Copy this code into that file
4. Import: import ScrollTextReveal from "@/components/ScrollTextReveal"
REQUIRED DEPENDENCIES:
- gsap (npm install gsap)
- GSAP ScrollTrigger plugin (included with gsap)
- GSAP SplitText plugin (included with gsap)
================================================================================
PROPS REFERENCE
================================================================================
| Prop | Type | Default | Description |
|-----------------|--------|------------------------|--------------------------------------|
| text | string | "Scroll to reveal..." | The text content to display |
| fontSize | string | "clamp(2rem,8vw,10rem)"| CSS font-size value |
| fontWeight | number | 600 | Font weight (100-900) |
| color | string | "#1a1a1a" | Text color |
| letterSpacing | string | "-0.02em" | Letter spacing |
| scrollDistance | number | 4500 | Scroll pixels for full animation |
| scatterY | number | 180 | Vertical scatter range (%) |
| scatterRotation | number | 25 | Rotation scatter range (degrees) |
| scatterScale | number | 0.8 | Initial scale (animates to 1) |
| initialOpacity | number | 0.3 | Initial opacity (animates to 1) |
| ease | string | "back.out(1.4)" | GSAP easing function |
| wordGap | string | "3.5vw" | Gap between words |
| className | string | "" | Additional wrapper classes |
| textClassName | string | "" | Additional text classes |
================================================================================
COMMON MODIFICATIONS
================================================================================
1. FASTER SCROLL (shorter distance):
<ScrollTextReveal scrollDistance={2500} />
2. MORE DRAMATIC SCATTER:
<ScrollTextReveal scatterY={300} scatterRotation={45} scatterScale={0.5} />
3. SUBTLE/MINIMAL EFFECT:
<ScrollTextReveal scatterY={50} scatterRotation={8} initialOpacity={0.6} ease="power2.out" />
4. CUSTOM TYPOGRAPHY:
<ScrollTextReveal
text="Your headline here"
fontSize="clamp(3rem, 12vw, 15rem)"
fontWeight={700}
color="#0066ff"
letterSpacing="-0.04em"
/>
5. DARK THEME:
<ScrollTextReveal text="Dark mode" color="#ffffff" />
================================================================================
SOURCE CODE
================================================================================
*/
"use client";
import React, { useRef, useEffect } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
gsap.registerPlugin(ScrollTrigger, SplitText);
export interface ScrollTextRevealProps {
/** The text content to display */
text: string;
/** Additional CSS classes for the wrapper section */
className?: string;
/** Additional CSS classes for the text element */
textClassName?: string;
/** Font size (CSS value, e.g., "clamp(2rem, 8vw, 10rem)") */
fontSize?: string;
/** Font weight (100-900) */
fontWeight?: number;
/** Text color (CSS color value) */
color?: string;
/** Letter spacing (CSS value, e.g., "-0.02em") */
letterSpacing?: string;
/** Total scroll distance in pixels before text fully scrolls through */
scrollDistance?: number;
/** Character vertical scatter range (percent of font size) */
scatterY?: number;
/** Character rotation scatter range (degrees) */
scatterRotation?: number;
/** Character scale scatter - animates from this value to 1 */
scatterScale?: number;
/** Character opacity - animates from this value to 1 */
initialOpacity?: number;
/** Easing function for character animations */
ease?: string;
/** Horizontal gap between words (CSS value) */
wordGap?: string;
}
export default function ScrollTextReveal({
text = "Scroll to reveal this amazing text animation",
className = "",
textClassName = "",
fontSize = "clamp(2rem, 8vw, 10rem)",
fontWeight = 600,
color = "#1a1a1a",
letterSpacing = "-0.02em",
scrollDistance = 4500,
scatterY = 180,
scatterRotation = 25,
scatterScale = 0.8,
initialOpacity = 0.3,
ease = "back.out(1.4)",
wordGap = "3.5vw",
}: ScrollTextRevealProps) {
const containerRef = useRef<HTMLDivElement>(null);
const textElementRef = useRef<HTMLHeadingElement>(null);
const splitInstanceRef = useRef<SplitText | null>(null);
const horizontalTweenRef = useRef<gsap.core.Tween | null>(null);
useEffect(() => {
const container = containerRef.current;
const textElement = textElementRef.current;
if (!container || !textElement) return;
// Split text into characters and words for animation
splitInstanceRef.current = SplitText.create(textElement, {
type: "chars, words",
});
// Create the horizontal scroll animation
horizontalTweenRef.current = gsap.to(textElement, {
xPercent: -100,
ease: "none",
scrollTrigger: {
trigger: container,
pin: true,
end: `+=${scrollDistance}px`,
scrub: true,
},
});
// Animate each character as it enters the viewport
splitInstanceRef.current.chars.forEach((char) => {
gsap.from(char, {
yPercent: `random(-${scatterY}, ${scatterY})`,
rotation: `random(-${scatterRotation}, ${scatterRotation})`,
scale: scatterScale,
opacity: initialOpacity,
ease: ease,
scrollTrigger: {
trigger: char,
containerAnimation: horizontalTweenRef.current!,
start: "left 95%",
end: "left 25%",
scrub: 1,
},
});
});
// Cleanup function
return () => {
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
if (splitInstanceRef.current) {
splitInstanceRef.current.revert();
}
};
}, [
text,
scrollDistance,
scatterY,
scatterRotation,
scatterScale,
initialOpacity,
ease,
]);
return (
<section
ref={containerRef}
className={`scroll-text-reveal-container overflow-hidden h-screen flex items-center ${className}`}
>
<h3
ref={textElementRef}
className={`scroll-text-reveal-text flex w-max whitespace-nowrap pl-[100vw] ${textClassName}`}
style={{
fontSize,
fontWeight,
lineHeight: 1.1,
color,
letterSpacing,
gap: wordGap,
}}
>
{text}
</h3>
</section>
);
}
export { ScrollTextReveal };import ScrollTextReveal from "@/components/ScrollTextReveal";
// BASIC USAGE
// Place this component where you want the horizontal scroll section
// It takes up full viewport height and scrolls as user scrolls down
export default function HeroPage() {
return (
<main>
{/* Content before the scroll reveal */}
<section className="h-screen flex items-center justify-center bg-gray-50">
<h1 className="text-4xl">Scroll down to see the magic</h1>
</section>
{/* The scroll reveal section */}
<ScrollTextReveal text="Welcome to something amazing" />
{/* Content after the scroll reveal */}
<section className="h-screen flex items-center justify-center bg-white">
<p className="text-xl">The journey continues...</p>
</section>
</main>
);
}
// CUSTOM STYLING
export function BrandedScrollText() {
return (
<ScrollTextReveal
text="Innovation starts here"
fontSize="clamp(3rem, 12vw, 15rem)"
fontWeight={700}
color="#0066ff"
letterSpacing="-0.04em"
scrollDistance={3500}
/>
);
}
// SUBTLE ANIMATION (minimal/elegant design)
export function SubtleScrollText() {
return (
<ScrollTextReveal
text="Elegance in motion"
fontSize="clamp(2rem, 6vw, 6rem)"
fontWeight={400}
color="#444"
scatterY={40}
scatterRotation={6}
scatterScale={0.95}
initialOpacity={0.5}
ease="power2.out"
scrollDistance={3000}
/>
);
}
// DRAMATIC EFFECT
export function DramaticScrollText() {
return (
<ScrollTextReveal
text="BREAKING NEWS"
fontSize="clamp(4rem, 15vw, 20rem)"
fontWeight={900}
color="#ff0000"
scatterY={350}
scatterRotation={50}
scatterScale={0.4}
initialOpacity={0.1}
scrollDistance={6000}
/>
);
}
// DARK THEME
export function DarkThemeScrollText() {
return (
<div className="bg-black">
<ScrollTextReveal
text="Night mode activated"
color="#ffffff"
scrollDistance={4000}
/>
</div>
);
}