83 lines
2.2 KiB
TypeScript
83 lines
2.2 KiB
TypeScript
import { useState, useCallback, useRef } from 'react';
|
|
|
|
const MATRIX_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%&*!?<>[]{}アイウエオカキクケコサシスセソタチツテトナニヌネノ';
|
|
|
|
interface GlitchTextProps {
|
|
text: string;
|
|
className?: string;
|
|
glitchOnHover?: boolean;
|
|
as?: 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'p';
|
|
}
|
|
|
|
const GlitchText = ({
|
|
text,
|
|
className = '',
|
|
glitchOnHover = true,
|
|
as: Component = 'span'
|
|
}: GlitchTextProps) => {
|
|
const [displayText, setDisplayText] = useState(text);
|
|
const [isGlitching, setIsGlitching] = useState(false);
|
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
const iterationRef = useRef(0);
|
|
|
|
const getRandomChar = () => {
|
|
return MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
|
|
};
|
|
|
|
const startGlitch = useCallback(() => {
|
|
if (!glitchOnHover || isGlitching) return;
|
|
|
|
setIsGlitching(true);
|
|
iterationRef.current = 0;
|
|
|
|
intervalRef.current = setInterval(() => {
|
|
setDisplayText(prev => {
|
|
return text
|
|
.split('')
|
|
.map((char, index) => {
|
|
if (char === ' ') return ' ';
|
|
// Gradually resolve characters from left to right
|
|
if (index < iterationRef.current) {
|
|
return text[index];
|
|
}
|
|
// Random chance to show original or glitch
|
|
return Math.random() > 0.5 ? getRandomChar() : char;
|
|
})
|
|
.join('');
|
|
});
|
|
|
|
iterationRef.current += 1;
|
|
|
|
// Complete after all characters resolved
|
|
if (iterationRef.current > text.length) {
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current);
|
|
}
|
|
setDisplayText(text);
|
|
setIsGlitching(false);
|
|
}
|
|
}, 50);
|
|
}, [text, glitchOnHover, isGlitching]);
|
|
|
|
const stopGlitch = useCallback(() => {
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current);
|
|
}
|
|
setDisplayText(text);
|
|
setIsGlitching(false);
|
|
}, [text]);
|
|
|
|
return (
|
|
<Component
|
|
className={`${className} ${isGlitching ? 'glitch-active' : ''}`}
|
|
onMouseEnter={startGlitch}
|
|
onMouseLeave={stopGlitch}
|
|
style={{ display: 'inline-block' }}
|
|
>
|
|
{displayText}
|
|
</Component>
|
|
);
|
|
};
|
|
|
|
export default GlitchText;
|