Files
portifolio-evandrodevbr/app/components/animations/CounterValue.tsx

65 lines
1.5 KiB
TypeScript

"use client";
import { useInView } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
interface CounterValueProps {
value: number;
className?: string;
duration?: number;
prefix?: string;
suffix?: string;
once?: boolean;
amount?: number;
}
export function CounterValue({
value,
className = "",
duration = 1200,
prefix = "",
suffix = "",
once = true,
amount = 0.45,
}: Readonly<CounterValueProps>) {
const shouldReduceMotion = useReducedMotion();
const ref = useRef<HTMLSpanElement | null>(null);
const isInView = useInView(ref, { once, amount });
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
if (!isInView) return;
if (shouldReduceMotion) {
setDisplayValue(value);
return;
}
const start = performance.now();
let rafId = 0;
const frame = (now: number) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setDisplayValue(Math.round(value * eased));
if (progress < 1) {
rafId = requestAnimationFrame(frame);
}
};
rafId = requestAnimationFrame(frame);
return () => cancelAnimationFrame(rafId);
}, [isInView, value, duration, shouldReduceMotion]);
return (
<span ref={ref} className={className}>
{prefix}
{displayValue}
{suffix}
</span>
);
}