55 lines
1.2 KiB
TypeScript
55 lines
1.2 KiB
TypeScript
import { motion } from 'framer-motion';
|
|
|
|
interface Props {
|
|
text: string;
|
|
className?: string;
|
|
delay?: number;
|
|
stagger?: number;
|
|
as?: 'span' | 'h1' | 'h2' | 'h3' | 'p' | 'div';
|
|
}
|
|
|
|
export default function SplitReveal({
|
|
text,
|
|
className = '',
|
|
delay = 0,
|
|
stagger = 0.035,
|
|
as = 'span',
|
|
}: Props) {
|
|
const chars = Array.from(text);
|
|
const MotionTag = motion[as] as typeof motion.span;
|
|
|
|
return (
|
|
<MotionTag
|
|
className={className}
|
|
initial="hidden"
|
|
animate="show"
|
|
aria-label={text}
|
|
variants={{
|
|
hidden: {},
|
|
show: {
|
|
transition: { staggerChildren: stagger, delayChildren: delay },
|
|
},
|
|
}}
|
|
>
|
|
{chars.map((ch, i) => (
|
|
<motion.span
|
|
key={i}
|
|
aria-hidden="true"
|
|
style={{ display: 'inline-block', whiteSpace: ch === ' ' ? 'pre' : 'normal' }}
|
|
variants={{
|
|
hidden: { opacity: 0, y: '0.4em', filter: 'blur(8px)' },
|
|
show: {
|
|
opacity: 1,
|
|
y: 0,
|
|
filter: 'blur(0px)',
|
|
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] },
|
|
},
|
|
}}
|
|
>
|
|
{ch === ' ' ? '\u00A0' : ch}
|
|
</motion.span>
|
|
))}
|
|
</MotionTag>
|
|
);
|
|
}
|