Docs
Counter
A counter component that counts up to given number. Works with both increment and decrement.
Installation
Update tailwind.config.js
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
}
},
},
Install framer-motion
npm install framer-motion
Run the following command
It will create a new file called counter.tsx
inside the components/animata/text
directory.
mkdir -p components/animata/text && touch components/animata/text/counter.tsx
Paste the code
Open the newly created file and paste the following code:
import { useEffect, useRef } from "react";
import { useInView, useMotionValue, useSpring } from "framer-motion";
import { cn } from "@/lib/utils";
interface CounterProps {
/**
* A function to format the counter value. By default, it will format the
* number with commas.
*/
format?: (value: number) => string;
/**
* The target value of the counter.
*/
targetValue: number;
/**
* The direction of the counter. If "up", the counter will start from 0 and
* go up to the target value. If "down", the counter will start from the target
* value and go down to 0.
*/
direction?: "up" | "down";
/**
* The delay in milliseconds before the counter starts counting.
*/
delay?: number;
/**
* Additional classes for the counter.
*/
className?: string;
}
export const Formatter = {
number: (value: number) => Intl.NumberFormat("en-US").format(+value.toFixed(0)),
currency: (value: number) =>
Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(+value.toFixed(0)),
};
export default function Counter({
format = Formatter.number,
targetValue,
direction = "up",
delay = 0,
className,
}: CounterProps) {
const ref = useRef<HTMLSpanElement>(null);
const isGoingUp = direction === "up";
const motionValue = useMotionValue(isGoingUp ? 0 : targetValue);
const springValue = useSpring(motionValue, {
damping: 60,
stiffness: 80,
});
const isInView = useInView(ref, { margin: "0px", once: true });
useEffect(() => {
if (!isInView) {
return;
}
const timer = setTimeout(() => {
motionValue.set(isGoingUp ? targetValue : 0);
}, delay);
return () => clearTimeout(timer);
}, [isInView, delay, isGoingUp, targetValue, motionValue]);
useEffect(() => {
springValue.on("change", (value) => {
if (ref.current) {
ref.current.textContent = format ? format(value) : value;
}
});
}, [springValue, format]);
return <span ref={ref} className={cn("text-4xl font-bold text-foreground", className)} />;
}
Credits
Built by hari