Text Reveal
Text slides up from behind a mask on scroll-into-view, word-by-word with stagger — cinematic editorial reveal
textscrollrevealmaskframer-motionstagger
Install dependencies
$npm install framer-motionPreview
Source Code
"use client";
import { useRef } from "react";
import { motion, useInView } from "framer-motion";
interface TextRevealProps {
text: string;
delay?: number;
stagger?: number;
duration?: number;
once?: boolean;
as?: "h1" | "h2" | "h3" | "p" | "span";
className?: string;
}
export default function TextReveal({
text,
delay = 0,
stagger = 0.08,
duration = 0.8,
once = false,
as = "span",
className = "",
}: TextRevealProps) {
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once, amount: 0.4 });
const words = text.split(" ");
const Tag = motion[as];
return (
<Tag
ref={ref}
className={className}
aria-label={text}
style={{ display: "inline-block" }}
>
{words.map((word, i) => (
<span
key={`${word}-${i}`}
aria-hidden="true"
style={{
display: "inline-block",
overflow: "hidden",
verticalAlign: "bottom",
paddingBottom: "0.1em",
marginBottom: "-0.1em",
}}
>
<motion.span
style={{ display: "inline-block", willChange: "transform" }}
initial={{ y: "110%" }}
animate={inView ? { y: "0%" } : { y: "110%" }}
transition={{
duration,
delay: delay + i * stagger,
ease: [0.22, 1, 0.36, 1],
}}
>
{word}
{i < words.length - 1 ? "\u00A0" : ""}
</motion.span>
</span>
))}
</Tag>
);
}