Scramble Text
Hover triggers rapid random-character cycling before resolving to the real text
animationtextscramblehoverdecode
Preview
Source Code
"use client";
import { useCallback, useRef, useState } from "react";
interface ScrambleTextProps {
text: string;
scrambleChars?: string;
speed?: number;
revealDelay?: number;
className?: string;
}
export default function ScrambleText({
text,
scrambleChars = "!@#$%&*<>[]{}/'",
speed = 30,
revealDelay = 50,
className = "",
}: ScrambleTextProps) {
const [display, setDisplay] = useState(text);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const isAnimatingRef = useRef(false);
const scramble = useCallback(() => {
if (isAnimatingRef.current) return;
isAnimatingRef.current = true;
let revealed = 0;
const chars = text.split("");
intervalRef.current = setInterval(() => {
const result = chars.map((char, i) => {
if (char === " ") return " ";
if (i < revealed) return char;
return scrambleChars[Math.floor(Math.random() * scrambleChars.length)];
});
setDisplay(result.join(""));
// Progressively reveal characters
if (Date.now() % revealDelay < speed) {
revealed++;
}
if (revealed >= chars.length) {
if (intervalRef.current) clearInterval(intervalRef.current);
setDisplay(text);
isAnimatingRef.current = false;
}
}, speed);
}, [text, scrambleChars, speed, revealDelay]);
const handleMouseEnter = () => {
scramble();
};
return (
<span
onMouseEnter={handleMouseEnter}
className={`inline-block cursor-default font-mono ${className}`}
>
{display}
</span>
);
}