Dotbase-site/components/ui/animations/typing-animation.tsx

84 lines
1.9 KiB
TypeScript
Raw Normal View History

2025-02-05 00:07:38 +05:30
"use client";
import { cn } from "@/lib/utils";
import { motion, MotionProps } from "framer-motion";
import { useEffect, useRef, useState } from "react";
interface TypingAnimationProps extends MotionProps {
children: string;
className?: string;
duration?: number;
delay?: number;
as?: React.ElementType;
startOnView?: boolean;
}
export function TypingAnimation({
children,
className,
duration = 50,
delay = 0,
as: Component = "div",
startOnView = true,
...props
}: TypingAnimationProps) {
const MotionComponent = motion.create(Component, {
forwardMotionProps: true,
});
const [displayedText, setDisplayedText] = useState<string>("");
const [started, setStarted] = useState(false);
const elementRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!startOnView) {
const startTimeout = setTimeout(() => {
setStarted(true);
}, delay);
return () => clearTimeout(startTimeout);
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
setStarted(true);
}, delay);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, [delay, startOnView]);
useEffect(() => {
if (!started) return;
let i = 0;
const typingEffect = setInterval(() => {
if (i < children.length) {
setDisplayedText(children.substring(0, i + 1));
i++;
} else {
clearInterval(typingEffect);
}
}, duration);
return () => {
clearInterval(typingEffect);
};
}, [children, duration, started]);
return (
<MotionComponent ref={elementRef} className={cn("", className)} {...props}>
{displayedText}
</MotionComponent>
);
}