- Gallery
- Creative & Unique
- AnimatedCounter
New
AnimatedCounter
Number counter that animates from 0 to target value using requestAnimationFrame with easing.
Creative & Uniquecounteranimationnumberstatsmotion
Dependencies
shadcn/ui components needed:
npx shadcn@latest add lucide-reactHow to use this component
Copy the code below into your project. Make sure you have the required shadcn/ui dependencies installed. Then import and use the component in your pages or layouts.
Code
1"use client";23import { useEffect, useState, useRef } from "react";4import { TrendingUp, Users, Star, Zap } from "lucide-react";56function useCounter(target: number, duration = 2000) {7 const [count, setCount] = useState(0);8 const frameRef = useRef<number>(0);910 useEffect(() => {11 const start = performance.now();12 const animate = (now: number) => {13 const elapsed = now - start;14 const progress = Math.min(elapsed / duration, 1);15 const eased = 1 - Math.pow(1 - progress, 3);16 setCount(Math.floor(eased * target));17 if (progress < 1) frameRef.current = requestAnimationFrame(animate);18 };19 frameRef.current = requestAnimationFrame(animate);20 return () => cancelAnimationFrame(frameRef.current);21 }, [target, duration]);2223 return count;24}2526const stats = [27 { label: "Active Users", value: 12847, prefix: "", suffix: "", icon: Users, color: "text-violet-600 dark:text-violet-400", bg: "bg-violet-50 dark:bg-violet-900/20" },28 { label: "Revenue", value: 94200, prefix: "$", suffix: "", icon: TrendingUp, color: "text-emerald-600 dark:text-emerald-400", bg: "bg-emerald-50 dark:bg-emerald-900/20" },29 { label: "Rating", value: 49, prefix: "", suffix: "", icon: Star, color: "text-amber-600 dark:text-amber-400", bg: "bg-amber-50 dark:bg-amber-900/20" },30 { label: "Uptime", value: 999, prefix: "", suffix: "%", icon: Zap, color: "text-blue-600 dark:text-blue-400", bg: "bg-blue-50 dark:bg-blue-900/20" },31];3233export default function AnimatedCounter() {34 return (35 <div className="mx-auto grid w-full max-w-2xl grid-cols-2 gap-4 md:grid-cols-4">36 {stats.map((stat) => {37 const count = useCounter(stat.value, 2500);38 const display = stat.label === "Rating" ? (count / 10).toFixed(1) : stat.label === "Uptime" ? (count / 10).toFixed(1) : count.toLocaleString();39 return (40 <div key={stat.label} className="rounded-2xl border border-zinc-200 bg-white p-5 text-center shadow-sm dark:border-zinc-800 dark:bg-zinc-950">41 <div className={`mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-xl ${stat.bg}`}>42 <stat.icon className={`h-5 w-5 ${stat.color}`} />43 </div>44 <p className={`text-2xl font-bold tabular-nums ${stat.color}`}>{stat.prefix}{display}{stat.suffix}</p>45 <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">{stat.label}</p>46 </div>47 );48 })}49 </div>50 );51}