ugh
This commit is contained in:
71
components/animated-counter.tsx
Normal file
71
components/animated-counter.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
export interface AnimatedCounterProps {
|
||||
value: number;
|
||||
duration?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
formatter?: (value: number) => string;
|
||||
}
|
||||
|
||||
export function AnimatedCounter({
|
||||
value,
|
||||
duration = 2000,
|
||||
prefix = "",
|
||||
suffix = "",
|
||||
formatter
|
||||
}: AnimatedCounterProps) {
|
||||
const [displayValue, setDisplayValue] = useState(0);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const frameRef = useRef<number | null>(null);
|
||||
|
||||
// Easing function for smoother animation
|
||||
const easeOutQuad = (t: number): number => t * (2 - t);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset start time on new value
|
||||
startTimeRef.current = null;
|
||||
|
||||
// Cancel any ongoing animation
|
||||
if (frameRef.current) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
}
|
||||
|
||||
// Animation function
|
||||
const animate = (timestamp: number) => {
|
||||
if (!startTimeRef.current) {
|
||||
startTimeRef.current = timestamp;
|
||||
}
|
||||
|
||||
const elapsed = timestamp - startTimeRef.current;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const easedProgress = easeOutQuad(progress);
|
||||
|
||||
const nextValue = Math.floor(easedProgress * value);
|
||||
setDisplayValue(nextValue);
|
||||
|
||||
if (progress < 1) {
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
} else {
|
||||
setDisplayValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
// Start the animation
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
if (frameRef.current) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
}
|
||||
};
|
||||
}, [value, duration]);
|
||||
|
||||
const formattedValue = formatter
|
||||
? formatter(displayValue)
|
||||
: `${prefix}${displayValue.toLocaleString()}${suffix}`;
|
||||
|
||||
return <span>{formattedValue}</span>;
|
||||
}
|
||||
Reference in New Issue
Block a user