Docs
Counter
A counter component that counts up to given number. Works with both increment and decrement.
Loading...
Installation
CLI
pnpm dlx shadcn@latest add https://animata.design/r/text/counter.json
Manual
Install motion
npm install motionRun 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.tsxPaste the code
Open the newly created file and paste the following code:
import { useInView, useMotionValue, useSpring } from "motion/react";
import { useEffect, useRef } from "react";
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 = 1000,
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) : String(value);
}
});
}, [springValue, format]);
const initialDisplay = format
? format(isGoingUp ? 0 : targetValue)
: String(isGoingUp ? 0 : targetValue);
return (
<span ref={ref} className={cn("text-4xl font-bold text-foreground", className)}>
{initialDisplay}
</span>
);
}Credits
Built by hari