Text Scramble
ui-beats/text-scrambleFreeComponent
The TextScramble component decodes text one character at a time, cycling through random glyphs before each letter settles into place.
Install it
npx shadcn@latest add https://uibeats.com/r/text-scramble.jsonSource
1"use client";23import { useCallback, useEffect, useRef, useState } from "react";4import { useReducedMotion } from "motion/react";56interface TextScrambleProps {7 text: string;8 /** Characters cycled through before a letter settles. */9 characters?: string;10 /** Milliseconds between scramble frames. */11 speed?: number;12 /** Frames each character scrambles before locking in. */13 scrambleCount?: number;14 /** Re-run the effect whenever the pointer enters the text. */15 scrambleOnHover?: boolean;16 className?: string;17}1819const DEFAULT_CHARS = "!<>-_\\/[]{}—=+*^?#________";2021/**22 * Decodes text one character at a time, cycling through random glyphs before23 * each letter settles.24 */25export function TextScramble({26 text,27 characters = DEFAULT_CHARS,28 speed = 40,29 scrambleCount = 6,30 scrambleOnHover = true,31 className = "",32}: TextScrambleProps) {33 const prefersReducedMotion = useReducedMotion();34 const [display, setDisplay] = useState(text);35 const [renderedText, setRenderedText] = useState(text);36 const frameRef = useRef<ReturnType<typeof setInterval> | null>(null);3738 // React's "adjust state when a prop changes" pattern: resetting during render39 // rather than in an effect avoids a wasted commit with stale text on screen.40 if (text !== renderedText) {41 setRenderedText(text);42 setDisplay(text);43 }4445 const stop = useCallback(() => {46 if (frameRef.current) {47 clearInterval(frameRef.current);48 frameRef.current = null;49 }50 }, []);5152 /**53 * Starts the scramble interval. Deliberately performs no synchronous state54 * update, so it is safe to call from an effect body — the timer is the55 * external system the effect subscribes to.56 */57 const run = useCallback(() => {58 if (prefersReducedMotion) return;5960 stop();61 let frame = 0;6263 frameRef.current = setInterval(() => {64 const revealed = Math.floor(frame / scrambleCount);6566 if (revealed >= text.length) {67 setDisplay(text);68 stop();69 return;70 }7172 const next = text73 .split("")74 .map((char, index) => {75 if (index < revealed || char === " ") return char;76 return (77 characters[Math.floor(Math.random() * characters.length)] ?? char78 );79 })80 .join("");8182 setDisplay(next);83 frame += 1;84 }, speed);85 }, [characters, prefersReducedMotion, scrambleCount, speed, stop, text]);8687// … truncated
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 |
