motion-primitives
components
ui
animation
motion

text-scramble

Text animation that transforms text by randomly cycling through characters before settling on the final content, creating an engaging cryptographic effect.

animated
effect
text
Docs

Source Code

Files
text-scramble.tsx
1'use client';
2import { type JSX, useEffect, useState } from 'react';
3import { motion, MotionProps } from 'motion/react';
4
5export type TextScrambleProps = {
6  children: string;
7  duration?: number;
8  speed?: number;
9  characterSet?: string;
10  as?: React.ElementType;
11  className?: string;
12  trigger?: boolean;
13  onScrambleComplete?: () => void;
14} & MotionProps;
15
16const defaultChars =
17  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
18
19export function TextScramble({
20  children,
21  duration = 0.8,
22  speed = 0.04,
23  characterSet = defaultChars,
24  className,
25  as: Component = 'p',
26  trigger = true,
27  onScrambleComplete,
28  ...props
29}: TextScrambleProps) {
30  const MotionComponent = motion.create(
31    Component as keyof JSX.IntrinsicElements
32  );
33  const [displayText, setDisplayText] = useState(children);
34  const [isAnimating, setIsAnimating] = useState(false);
35  const text = children;
36
37  const scramble = async () => {
38    if (isAnimating) return;
39    setIsAnimating(true);
40
41    const steps = duration / speed;
42    let step = 0;
43
44    const interval = setInterval(() => {
45      let scrambled = '';
46      const progress = step / steps;
47
48      for (let i = 0; i < text.length; i++) {
49        if (text[i] === ' ') {
50          scrambled += ' ';
51          continue;
52        }
53
54        if (progress * text.length > i) {
55          scrambled += text[i];
56        } else {
57          scrambled +=
58            characterSet[Math.floor(Math.random() * characterSet.length)];
59        }
60      }
61
62      setDisplayText(scrambled);
63      step++;
64
65      if (step > steps) {
66        clearInterval(interval);
67        setDisplayText(text);
68        setIsAnimating(false);
69        onScrambleComplete?.();
70      }
71    }, speed * 1000);
72  };
73
74  useEffect(() => {
75    if (!trigger) return;
76
77    scramble();
78  }, [trigger]);
79
80  return (
81    <MotionComponent className={className} {...props}>
82      {displayText}
83    </MotionComponent>
84  );
85}
86