Create a Scrambled Text Effect in React
Scramble text into random characters on hover and restore it, creating a glitchy interactive effect for headings and labels.
Hover over a heading and watch it lose its mind for a second before snapping back. That micro-moment of chaos is what makes Scrambled Text memorable. It is interactive, slightly unpredictable, and makes even simple text feel like it has personality.
The final result
ScrambledText listens for pointer movement over the text. Characters within a configurable radius of the cursor scramble through random characters using GSAP's ScrambleTextPlugin, with intensity based on distance from the cursor.
Setting up
This component requires GSAP with two plugins: SplitText and ScrambleTextPlugin. Both are GSAP Club plugins.
import { gsap } from 'gsap';
import { SplitText } from 'gsap/SplitText';
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';
gsap.registerPlugin(SplitText, ScrambleTextPlugin);Building the component
The props are kept intentionally minimal.
interface ScrambledTextProps {
radius?: number;
duration?: number;
speed?: number;
scrambleChars?: string;
className?: string;
style?: React.CSSProperties;
children: React.ReactNode;
}In useEffect, we split the paragraph into characters and store each character's original content in a data-content attribute. This gives us a stable reference to restore the text to after scrambling.
const split = SplitText.create(rootRef.current.querySelector('p'), {
type: 'chars',
charsClass: 'inline-block will-change-transform',
});
split.chars.forEach(el => {
const c = el as HTMLElement;
gsap.set(c, { attr: { 'data-content': c.innerHTML } });
});The pointer move handler calculates the distance from the cursor to each character's center. Characters within the radius get a scrambleText tween applied. The duration of the tween scales with proximity: closer characters scramble for longer.
const handleMove = (e: PointerEvent) => {
split.chars.forEach(el => {
const c = el as HTMLElement;
const { left, top, width, height } = c.getBoundingClientRect();
const dx = e.clientX - (left + width / 2);
const dy = e.clientY - (top + height / 2);
const dist = Math.hypot(dx, dy);
if (dist < radius) {
gsap.to(c, {
overwrite: true,
duration: duration * (1 - dist / radius),
scrambleText: {
text: c.dataset.content || '',
chars: scrambleChars,
speed,
},
ease: 'none',
});
}
});
};
el.addEventListener('pointermove', handleMove);Using overwrite: true on the tween prevents multiple tweens from stacking up on the same element as the cursor moves, which would cause erratic behavior.
How to use it
<ScrambledText
radius={100}
duration={1.2}
speed={0.5}
scrambleChars=".:"
>
Move your cursor across this text to see it scramble.
</ScrambledText>The scrambleChars prop controls which characters are used during scrambling. ".:" gives a subtle dot-and-colon noise. Use alphanumeric characters for a more dramatic effect.
Key takeaways
- Scaling the tween duration by
1 - dist / radiusmeans characters directly under the cursor scramble the longest, while those at the edge of the radius scramble briefly. This creates a natural falloff. - Using
Math.hypotis a clean way to calculate Euclidean distance from two deltas without manual squaring and square-rooting. - The
SplitText.createAPI from GSAP 3.12+ is the preferred way to instantiate splits, and its cleanup viasplit.revert()in the effect cleanup prevents memory leaks.