Text Scramble
Installation
Install the following dependencies:
pnpm add motion
Create a file at components/motion/text-scramble.tsx
and paste the given
code.
"use client";
import { type JSX, useEffect, useState } from "react";
import { motion, MotionProps } from "motion/react";
export type TextScrambleProps = {
children: string;
duration?: number;
speed?: number;
characterSet?: string;
as?: React.ElementType;
className?: string;
trigger?: boolean;
onScrambleComplete?: () => void;
} & MotionProps;
const defaultChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
export function TextScramble({
children,
duration = 0.8,
speed = 0.04,
characterSet = defaultChars,
className,
as: Component = "p",
trigger = true,
onScrambleComplete,
...props
}: TextScrambleProps) {
const MotionComponent = motion.create(
Component as keyof JSX.IntrinsicElements,
);
const [displayText, setDisplayText] = useState(children);
const [isAnimating, setIsAnimating] = useState(false);
const text = children;
const scramble = async () => {
if (isAnimating) return;
setIsAnimating(true);
const steps = duration / speed;
let step = 0;
const interval = setInterval(() => {
let scrambled = "";
const progress = step / steps;
for (let i = 0; i < text.length; i++) {
if (text[i] === " ") {
scrambled += " ";
continue;
}
if (progress * text.length > i) {
scrambled += text[i];
} else {
scrambled +=
characterSet[
Math.floor(Math.random() * characterSet.length)
];
}
}
setDisplayText(scrambled);
step++;
if (step > steps) {
clearInterval(interval);
setDisplayText(text);
setIsAnimating(false);
onScrambleComplete?.();
}
}, speed * 1000);
};
useEffect(() => {
if (!trigger) return;
scramble();
}, [trigger]);
return (
<MotionComponent className={className} {...props}>
{displayText}
</MotionComponent>
);
}