Getting Started
IntroductionLayout Components
Expandable PanelText Scramble Reveal
An interactive text component that scrambles characters on mouse proximity. Features customizable scramble characters, proximity detection, and smooth animations with pure React.
React
Animation
Interactive
Typography
Did you know that honey never spoils? Archaeologists have found pots of honey in ancient Egyptian tombs that are over 3,000 years old and still perfectly edible! This is because honeys unique chemical composition and low moisture content make it nearly impossible for bacteria and microorganisms to survive in it.Did you know that honey never spoils? Archaeologists have found pots of honey in ancient Egyptian tombs that are over 3,000 years old and still perfectly edible! This is because honeys unique chemical composition and low moisture content make it nearly impossible for bacteria and microorganisms to survive in it.Did you know that honey never spoils? Archaeologists have found pots of honey in ancient Egyptian tombs that are over 3,000 years old and still perfectly edible! This is because honeys unique chemical composition and low moisture content make it nearly impossible for bacteria and microorganisms to survive in it.Did you know that honey never spoils? Archaeologists have found pots of honey in ancient Egyptian tombs that are over 3,000 years old and still perfectly edible! This is because honeys unique chemical composition and low moisture content make it nearly impossible for bacteria and microorganisms to survive in it.
Using CLI
npx dimaac add TextScrambleReveal
Manual Installation
npm install react
lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
components/ui/TextScrambleReveal.tsx
'use client';
import { useRef, useEffect, useState } from 'react';
interface TextScrambleRevealProps {
children: React.ReactNode;
className?: string;
maxWidth?: string;
fontSize?: string;
lineHeight?: number;
textColor?: string;
scrambleColor?: string;
scrambleChars?: string;
proximityRadius?: number;
backgroundColor?: string;
fontFamily?: string;
}
const TextScrambleReveal: React.FC<TextScrambleRevealProps> = ({
children,
className = '',
maxWidth = '800px',
fontSize = '30px',
lineHeight = 1.5,
textColor = '#ffffffb4',
scrambleColor = '#00c8ff',
scrambleChars = '.:',
proximityRadius = 100,
backgroundColor = 'transparent',
fontFamily = '"Poppins", monospace',
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLDivElement>(null);
const [chars, setChars] = useState<HTMLElement[]>([]);
const scrambleChar = (element: HTMLElement, originalText: string) => {
const scrambleCharsArray = scrambleChars.split('');
let iterations = 0;
const maxIterations = 8;
const animate = () => {
if (iterations < maxIterations) {
const randomChar = scrambleCharsArray[Math.floor(Math.random() * scrambleCharsArray.length)];
element.textContent = randomChar;
iterations++;
setTimeout(animate, 50);
} else {
element.textContent = originalText;
element.classList.remove('scrambling');
element.style.color = textColor;
}
};
element.classList.add('scrambling');
element.style.color = scrambleColor;
console.log(scrambleColor)
animate();
};
useEffect(() => {
if (!textRef.current) return;
const textContent = textRef.current.textContent || '';
const charElements: HTMLElement[] = [];
textRef.current.innerHTML = '';
textContent.split('').forEach((char) => {
const span = document.createElement('span');
span.className = 'char';
span.textContent = char;
span.dataset.content = char;
span.style.display = char === ' ' ? 'inline' : 'inline-block';
textRef.current?.appendChild(span);
charElements.push(span);
});
setChars(charElements);
}, [children]);
useEffect(() => {
if (!containerRef.current || chars.length === 0) return;
const handlePointerMove = (e: PointerEvent) => {
chars.forEach(char => {
const rect = char.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const x = e.clientX - centerX;
const y = e.clientY - centerY;
const distance = Math.sqrt(x * x + y * y);
if (distance < proximityRadius && !char.classList.contains('scrambling')) {
const originalText = char.dataset.content || char.textContent || '';
scrambleChar(char, originalText);
}
});
};
containerRef.current.addEventListener('pointermove', handlePointerMove);
return () => {
const container = containerRef.current;
if (container) {
container.removeEventListener('pointermove', handlePointerMove);
}
};
}, [chars, proximityRadius, scrambleChar]);
return (
<div
ref={containerRef}
className={`text-scramble ${className}`}
style={{
maxWidth,
fontFamily,
fontSize,
lineHeight,
color: textColor,
background: backgroundColor,
userSelect: 'none',
}}
>
<div ref={textRef}>
{typeof children === 'string' ? children : ''}
</div>
</div>
);
};
export default TextScrambleReveal;