Scroll Reveal
ui-beats/scroll-revealFreeComponent
The ScrollReveal component lights up text word by word as the reader scrolls past it, tied to scroll position rather than a timer.
Install it
npx shadcn@latest add https://uibeats.com/r/scroll-reveal.jsonSource
1"use client";23import {4 motion,5 useScroll,6 useTransform,7 useReducedMotion,8 type MotionValue,9} from "motion/react";10import { useRef } from "react";1112interface ScrollRevealProps {13 children: string;14 /** Opacity of a word before it is reached. */15 restingOpacity?: number;16 /** How much of the viewport the reveal is spread across, 0-1. */17 spread?: number;18 className?: string;19}2021/**22 * Text that lights up word by word as the reader scrolls past it.23 *24 * Each word maps its own slice of the container's scroll progress, so the25 * reveal tracks the scrollbar exactly — scroll back up and it un-reveals.26 * A time-based stagger would drift out of sync with the reader.27 */28export function ScrollReveal({29 children,30 restingOpacity = 0.15,31 spread = 0.5,32 className = "",33}: ScrollRevealProps) {34 const ref = useRef<HTMLParagraphElement>(null);35 const prefersReducedMotion = useReducedMotion();3637 const { scrollYProgress } = useScroll({38 target: ref,39 offset: ["start 0.9", `end ${spread}`],40 });4142 const words = children.split(" ");4344 if (prefersReducedMotion) {45 return <p className={className}>{children}</p>;46 }4748 return (49 <p ref={ref} className={`flex flex-wrap ${className}`}>50 {words.map((word, index) => {51 const start = index / words.length;52 const end = start + 1 / words.length;5354 return (55 <Word56 key={`${word}-${index}`}57 progress={scrollYProgress}58 range={[start, end]}59 restingOpacity={restingOpacity}60 >61 {word}62 </Word>63 );64 })}65 </p>66 );67}6869interface WordProps {70 children: string;71 progress: MotionValue<number>;72 range: [number, number];73 restingOpacity: number;74}7576function Word({ children, progress, range, restingOpacity }: WordProps) {77 const opacity = useTransform(progress, range, [restingOpacity, 1]);78 // A touch of blur on the way in makes the arrival read as focus rather than79 // as a flat fade.80 const filter = useTransform(progress, range, [81 "blur(4px)",82 "blur(0px)",83 ] as const);8485 return (86 <span className="relative mr-[0.25em] inline-block">87 <motion.span style={{ opacity, filter }}>{children}</motion.span>88 </span>89 );90}9192export default ScrollReveal;
What it pulls in
npm packages
Files it writes
More from ui-beats
All 34 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Beamanimated-beam | ui-beats | Components | Free | 1 dep | |
| Animated Listanimated-list | ui-beats | Components | Free | 1 dep | |
| Border Beamborder-beam | ui-beats | Components | Free | 1 dep | |
| Bouncebounce | ui-beats | Components | Free | 1 dep | |
| Card Stackcard-stack | ui-beats | Components | Free | 1 dep | |
| Dockdock | ui-beats | Components | Free | 1 dep | |
| Fade Infade-in | ui-beats | Components | Free | 1 dep | |
| Fade In Unblurfade-in-unblur | ui-beats | Components | Free | 1 dep |
