Skip to content

Scramble Text

Text that decodes from random characters into its real word on hover.

Freebrutalistneon
Category
Text Effects
Added
2026-08-21
Deps
none
Updated
2026-09-01
Preview

Props

30
No runtime dependencies
"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>
  );
}
Notes

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.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Glitch Text Hover

text effects

An RGB-split, jittery glitch effect on text when you hover it.

textglitchhover
Trending
New

Extruded Type Tilt

text effects

Display type with a solid extrusion that swings around as the pointer moves.

text3dextrude
Featured
NewPro

RGB channels of a headline separate toward the pointer and recombine to white.

textchromatichover