@gfazioli/mantine-text-animate
Version:
The TextAnimate component allows you to animate text with various effects.
143 lines (140 loc) • 3.98 kB
JavaScript
'use client';
import { useState, useRef, useEffect, useCallback } from 'react';
function useTypewriter(options) {
const {
value,
animate = true,
multiline = false,
speed = 0.03,
delay = 2e3,
loop = true,
onTypeEnd,
onTypeLoop
} = options;
const textArray = Array.isArray(value) ? value : [value];
const [displayText, setDisplayText] = useState("");
const [completedLines, setCompletedLines] = useState([]);
const [currentTextIndex, setCurrentTextIndex] = useState(0);
const [isTyping, setIsTyping] = useState(true);
const [isDeleting, setIsDeleting] = useState(false);
const [isActive, setIsActive] = useState(animate);
const timeoutRef = useRef(null);
const prevAnimateRef = useRef(animate);
const currentFullText = textArray[currentTextIndex];
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
useEffect(() => {
if (!prevAnimateRef.current && animate) {
setIsActive(true);
}
if (prevAnimateRef.current && !animate) {
setIsActive(false);
setDisplayText("");
setCompletedLines([]);
setCurrentTextIndex(0);
setIsTyping(true);
setIsDeleting(false);
}
prevAnimateRef.current = animate;
}, [animate]);
const reset = useCallback(() => {
setDisplayText("");
setCompletedLines([]);
setCurrentTextIndex(0);
setIsTyping(true);
setIsDeleting(false);
}, []);
const start = useCallback(() => {
setIsActive(true);
}, []);
const stop = useCallback(() => {
setIsActive(false);
}, []);
useEffect(() => {
if (textArray.length === 0 || !isActive) return;
if (isTyping && !isDeleting) {
if (displayText.length < currentFullText.length) {
timeoutRef.current = setTimeout(() => {
setDisplayText(currentFullText.substring(0, displayText.length + 1));
}, speed * 1e3);
} else {
setIsTyping(false);
const isLastText = currentTextIndex === textArray.length - 1;
if (multiline) {
timeoutRef.current = setTimeout(() => {
setCompletedLines((prev) => [...prev, displayText]);
setDisplayText("");
if (isLastText && loop) {
setCompletedLines([]);
setCurrentTextIndex(0);
onTypeLoop?.();
} else if (!isLastText) {
setCurrentTextIndex((prev) => prev + 1);
}
if (!isLastText || loop) {
setIsTyping(true);
} else {
onTypeEnd?.();
}
}, delay);
} else {
if ((!isLastText || loop) && !multiline) {
timeoutRef.current = setTimeout(() => {
setIsDeleting(true);
setIsTyping(true);
}, delay);
}
if (isLastText && loop) {
onTypeLoop?.();
}
if (isLastText && !loop) {
onTypeEnd?.();
}
}
}
}
if (isTyping && isDeleting) {
if (displayText.length > 0) {
timeoutRef.current = setTimeout(() => {
setDisplayText(displayText.substring(0, displayText.length - 1));
}, speed * 500);
} else {
setIsDeleting(false);
if (currentTextIndex === textArray.length - 1 && !loop) {
setCurrentTextIndex(0);
} else {
setCurrentTextIndex((prev) => (prev + 1) % textArray.length);
}
}
}
}, [
displayText,
isActive,
isTyping,
isDeleting,
currentFullText,
multiline,
textArray,
currentTextIndex,
speed,
delay,
loop,
onTypeEnd,
onTypeLoop
]);
const outputText = multiline ? [...completedLines, displayText] : displayText;
return {
text: outputText,
isTyping: isTyping && isActive,
start,
stop,
reset
};
}
export { useTypewriter };
//# sourceMappingURL=use-typewriter.mjs.map