Scramble Text
loomui/scramble-textFreeComponent
A string that resolves out of random glyphs, left to right, on mount, on scroll, or on hover.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/scramble-text.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"67export interface ScrambleTextProps extends Omit<8 React.ComponentProps<"span">,9 "children"10> {11 /** The string the run resolves to. */12 text: string13 /** Glyphs drawn in a slot that has not settled yet. */14 characters?: string15 /** Milliseconds per scramble frame. */16 speed?: number17 /** Frames a slot scrambles before it settles. */18 cycles?: number19 /** Frames between one slot settling and the next. */20 stagger?: number21 /** What starts a run. `hover` starts a fresh one on every enter. */22 trigger?: "mount" | "view" | "hover"23 /** Render the finished text with no scramble. */24 disabled?: boolean25}2627const DEFAULT_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@*<>/"2829// `useEffect` would let the finished text paint for a frame before the first30// scramble frame replaced it, and `useLayoutEffect` alone warns during SSR.31const useIsomorphicLayoutEffect =32 typeof window === "undefined" ? React.useEffect : React.useLayoutEffect3334export function ScrambleText({35 text,36 characters = DEFAULT_CHARACTERS,37 speed = 45,38 cycles = 8,39 stagger = 2,40 trigger = "mount",41 disabled = false,42 className,43 ...props44}: ScrambleTextProps) {45 const ref = React.useRef<HTMLSpanElement>(null)4647 // The first render is the finished text, so the server and the client agree48 // and the component degrades to plain text without JavaScript.49 const [display, setDisplay] = React.useState(text)50 const [run, setRun] = React.useState(0)5152 const start = React.useCallback(() => setRun((value) => value + 1), [])5354 React.useEffect(() => {55 if (trigger === "mount") {56 start()57 return58 }59 if (trigger !== "view") {60 return61 }6263 const node = ref.current64 if (!node) {65 return66 }6768 const observer = new IntersectionObserver(69 (entries) => {70 if (entries.some((entry) => entry.isIntersecting)) {71 start()72 observer.disconnect()73 }74 },75 { threshold: 0.3 }76 )77 observer.observe(node)78 return () => observer.disconnect()79 }, [trigger, text, start])8081 useIsomorphicLayoutEffect(() => {82 if (run === 0) {83 return84 }85 if (86 disabled ||87 window.matchMedia("(prefers-reduced-motion: reduce)").matches88 ) {89 setDisplay(text)90 return91 }9293 const slots = Array.from(text)94 const pool = characters.length > 0 ? characters : DEFAULT_CHARACTERS95// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
