Added animated stats from 0 to their actual value.
This commit is contained in:
parent
36f509f6bc
commit
aacf530b81
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
30
app/page.tsx
30
app/page.tsx
|
|
@ -20,12 +20,13 @@ import {
|
|||
CheckIcon,
|
||||
RecycleIcon,
|
||||
} from "./components/Icons";
|
||||
import AnimatedCounter from "./components/AnimatedCounter";
|
||||
|
||||
const stats = [
|
||||
{ value: "100+", label: "Years Combined Experience" },
|
||||
{ value: "10", label: "States Covered" },
|
||||
{ value: "2005", label: "Industry Trusted Since" },
|
||||
{ value: "5", label: "Product Categories" },
|
||||
{ value: 100, suffix: "+", label: "Years Combined Experience" },
|
||||
{ value: 10, suffix: "", label: "States Covered" },
|
||||
{ value: 2005, suffix: "", label: "Industry Trusted Since" },
|
||||
{ value: 5, suffix: "", label: "Product Categories" },
|
||||
];
|
||||
|
||||
const productCategories = [
|
||||
|
|
@ -271,20 +272,13 @@ export default function Home() {
|
|||
whileHover={{ scale: 1.05 }}
|
||||
className="group cursor-default"
|
||||
>
|
||||
<motion.span
|
||||
className="block text-4xl sm:text-5xl font-bold text-white mb-2 group-hover:text-[hsl(0,100%,60%)] transition-colors"
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 15,
|
||||
delay: index * 0.1,
|
||||
}}
|
||||
>
|
||||
{stat.value}
|
||||
</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">
|
||||
<AnimatedCounter
|
||||
value={stat.value}
|
||||
suffix={stat.suffix}
|
||||
duration={2}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm sm:text-base">
|
||||
{stat.label}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue