landing-page/app/components/AnimatedCounter.tsx

59 lines
1.3 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { useInView, useSpring, useTransform, motion } from "framer-motion";
interface AnimatedCounterProps {
value: number;
suffix?: string;
prefix?: string;
duration?: number;
className?: string;
}
export default function AnimatedCounter({
value,
suffix = "",
prefix = "",
duration = 2,
className = "",
}: AnimatedCounterProps) {
const ref = useRef<HTMLSpanElement>(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const [hasAnimated, setHasAnimated] = useState(false);
const spring = useSpring(0, {
stiffness: 50,
damping: 30,
duration: duration * 1000,
});
const display = useTransform(spring, (current) => {
return Math.round(current);
});
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
if (isInView && !hasAnimated) {
setHasAnimated(true);
spring.set(value);
}
}, [isInView, hasAnimated, spring, value]);
useEffect(() => {
const unsubscribe = display.on("change", (v) => {
setDisplayValue(v);
});
return unsubscribe;
}, [display]);
return (
<motion.span ref={ref} className={className}>
{prefix}
{displayValue}
{suffix}
</motion.span>
);
}