Skip to content
Plinth

Original by Plinth · MIT

Text Effects

Scramble Text

A word that dissolves into glyphs and resolves back on hover.

#text#scramble#hover#javascript

Install

npx shadcn@latest add https://plinthui.com/r/scramble-text.json
Open in v0
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';

const GLYPHS = '!<>-_\\/[]{}—=+*^?#';
const STEP = 40; // ms between glyph cycles
const STAGGER = 3; // extra cycles each character waits before locking

export function ScrambleText({ text = 'DECRYPT' }: { text?: string }) {
  const [display, setDisplay] = useState(text);
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const mountedRef = useRef(true);

  const stop = useCallback(() => {
    if (timerRef.current !== null) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  }, []);

  // Clearing first means hovering mid-run restarts cleanly instead of
  // leaving two intervals writing to the same span.
  const run = useCallback(() => {
    stop();
    let frame = 0;
    timerRef.current = setInterval(() => {
      if (!mountedRef.current) {
        stop();
        return;
      }
      let out = '';
      for (let i = 0; i < text.length; i += 1) {
        out +=
          frame >= (i + 1) * STAGGER
            ? text.charAt(i)
            : GLYPHS.charAt(Math.floor(Math.random() * GLYPHS.length));
      }
      setDisplay(out);
      if (frame >= text.length * STAGGER) stop();
      frame += 1;
    }, STEP);
  }, [stop, text]);

  useEffect(() => {
    mountedRef.current = true;
    run();
    return () => {
      mountedRef.current = false;
      stop();
    };
  }, [run, stop]);

  return (
    <span
      onMouseEnter={run}
      style={{ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace' }}
      className="cursor-default text-[24px] tracking-[0.1em] text-[#1a1a1a] dark:text-[#ece9e2]"
    >
      {display}
    </span>
  );
}

More in Text Effects

Similar tags