import React, { useState, useEffect } from 'react'; export default function TypingEffect({ steps = [], loop = true, typingSpeed = [40, 120], deleteSpeed = [20, 60], }) { const [displayed, setDisplayed] = useState(''); const [showCursor, setShowCursor] = useState(true); const [cursorBlink, setCursorBlink] = useState(true); const configKey = JSON.stringify({ steps, loop, typingSpeed, deleteSpeed }); useEffect(() => { let cancelled = false; let timerId = null; const sleep = (ms) => new Promise((resolve) => { timerId = setTimeout(resolve, ms); }); const rand = ([min, max]) => Math.floor(min + Math.random() * (max - min + 1)); const run = async () => { do { let chars = []; setDisplayed(''); for (const step of steps) { if (cancelled) return; if ('text' in step) { const speed = step.speed || typingSpeed; for (const c of Array.from(step.text)) { if (cancelled) return; chars.push(c); setDisplayed(chars.join('')); await sleep(rand(speed)); } } else if ('pause' in step) { await sleep(step.pause); } else if ('delete' in step) { const speed = step.speed || deleteSpeed; const count = step.delete === 'all' ? chars.length : Math.min(step.delete, chars.length); for (let i = 0; i < count; i++) { if (cancelled) return; chars.pop(); setDisplayed(chars.join('')); await sleep(rand(speed)); } } } if (!loop) { setShowCursor(false); return; } await sleep(500); } while (!cancelled); }; run(); return () => { cancelled = true; if (timerId != null) clearTimeout(timerId); }; }, [configKey]); useEffect(() => { if (!showCursor) return; const id = setInterval(() => setCursorBlink((v) => !v), 530); return () => clearInterval(id); }, [showCursor]); const parts = displayed.split('\n'); return ( {parts.map((part, i) => ( {i > 0 &&
} {part}
))} {showCursor && ( ); }