Added animated stats from 0 to their actual value.

This commit is contained in:
Andrés Mora 2025-12-19 06:55:45 -05:00
parent 36f509f6bc
commit aacf530b81
2 changed files with 70 additions and 18 deletions

View File

@ -0,0 +1,58 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useInView, useSpring, useTransform, motion } from "framer-motion";
interface AnimatedCounterProps {
value: number;
suffix?: string;
prefix?: string;
duration?: number;
className?: string;
}
export default function AnimatedCounter({
value,
suffix = "",
prefix = "",
duration = 2,
className = "",
}: AnimatedCounterProps) {
const ref = useRef<HTMLSpanElement>(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const [hasAnimated, setHasAnimated] = useState(false);
const spring = useSpring(0, {
stiffness: 50,
damping: 30,
duration: duration * 1000,
});
const display = useTransform(spring, (current) => {
return Math.round(current);
});
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
if (isInView && !hasAnimated) {
setHasAnimated(true);
spring.set(value);
}
}, [isInView, hasAnimated, spring, value]);
useEffect(() => {
const unsubscribe = display.on("change", (v) => {
setDisplayValue(v);
});
return unsubscribe;
}, [display]);
return (
<motion.span ref={ref} className={className}>
{prefix}
{displayValue}
{suffix}
</motion.span>
);
}

View File

@ -20,12 +20,13 @@ import {
CheckIcon, CheckIcon,
RecycleIcon, RecycleIcon,
} from "./components/Icons"; } from "./components/Icons";
import AnimatedCounter from "./components/AnimatedCounter";
const stats = [ const stats = [
{ value: "100+", label: "Years Combined Experience" }, { value: 100, suffix: "+", label: "Years Combined Experience" },
{ value: "10", label: "States Covered" }, { value: 10, suffix: "", label: "States Covered" },
{ value: "2005", label: "Industry Trusted Since" }, { value: 2005, suffix: "", label: "Industry Trusted Since" },
{ value: "5", label: "Product Categories" }, { value: 5, suffix: "", label: "Product Categories" },
]; ];
const productCategories = [ const productCategories = [
@ -271,20 +272,13 @@ export default function Home() {
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
className="group cursor-default" className="group cursor-default"
> >
<motion.span <div className="block text-4xl sm:text-5xl font-bold text-white mb-2 group-hover:text-[hsl(0,100%,60%)] transition-colors">
className="block text-4xl sm:text-5xl font-bold text-white mb-2 group-hover:text-[hsl(0,100%,60%)] transition-colors" <AnimatedCounter
initial={{ opacity: 0, scale: 0.5 }} value={stat.value}
whileInView={{ opacity: 1, scale: 1 }} suffix={stat.suffix}
viewport={{ once: true }} duration={2}
transition={{ />
type: "spring", </div>
stiffness: 200,
damping: 15,
delay: index * 0.1,
}}
>
{stat.value}
</motion.span>
<span className="text-neutral-400 text-sm sm:text-base"> <span className="text-neutral-400 text-sm sm:text-base">
{stat.label} {stat.label}
</span> </span>