Scramble Text
Text that decodes from random characters into its real word on hover.
- Category
- Text Effects
- Added
- 2026-08-21
- Deps
- none
- Updated
- 2026-09-01
Props
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*";
export function ScrambleText({
text,
speed = 30,
trigger = "hover",
}: {
text: string;
/** ms between scramble frames. Lower is faster and busier. */
speed?: number;
trigger?: "hover" | "mount";
}) {
const [display, setDisplay] = useState(text);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
// A timer rather than requestAnimationFrame: the tick RATE is the whole
// effect here, and rAF pins it to the display's refresh rate instead.
const scramble = useCallback(() => {
let iteration = 0;
if (timer.current) clearTimeout(timer.current);
function tick() {
setDisplay(
text
.split("")
.map((char, i) => {
if (char === " ") return " ";
if (i < iteration) return text[i];
return CHARS[Math.floor(Math.random() * CHARS.length)];
})
.join(""),
);
if (iteration >= text.length) return;
// A third of a character per tick, so each letter is scrambled a few
// times before it locks — resolving one per tick reads as a typewriter.
iteration += 1 / 3;
timer.current = setTimeout(tick, speed);
}
tick();
}, [text, speed]);
useEffect(() => {
if (trigger === "mount") scramble();
return () => {
if (timer.current) clearTimeout(timer.current);
};
}, [trigger, scramble]);
return (
<button
onMouseEnter={() => trigger === "hover" && scramble()}
className="font-mono text-2xl font-bold tracking-tight"
>
{display}
</button>
);
}
About this text effect
The hacker-movie decode effect, random characters resolving left to right into real text, is simple in concept but easy to get wrong on timing. This drives it with a single fractional iteration counter advanced by a third of a character per animation frame rather than per-character timers: any index below the current iteration shows its real character, anything above it gets a fresh random pick from the charset every frame, and spaces are always preserved untouched so multi-word phrases do not scramble their whitespace. It is pure requestAnimationFrame with no dependency beyond React itself, no GSAP, no scramble library. Monospace is load-bearing, not a style preference: a proportional font's character widths shift as letters swap in and out mid-scramble, which reads as jitter rather than a stable decode. The catch is the trigger is only wired to onMouseEnter here, so on a touch device it never fires and the text simply never animates unless you rewire it to also fire on tap or focus. There is also no guard against re-triggering mid-scramble, so repeated hovers restart it from scratch.
Curator’s note
Monospace is load-bearing here, not a style choice — proportional fonts jitter width as characters swap and it ruins the illusion.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Glitch Text Hover
text effectsAn RGB-split, jittery glitch effect on text when you hover it.
Extruded Type Tilt
text effectsDisplay type with a solid extrusion that swings around as the pointer moves.
Chromatic Split Hover
text effectsRGB channels of a headline separate toward the pointer and recombine to white.