108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
interface CounterProps {
|
|
end: number;
|
|
duration?: number;
|
|
}
|
|
|
|
const Counter: React.FC<CounterProps> = ({ end, duration = 2000 }) => {
|
|
const [count, setCount] = useState(0);
|
|
const countRef = useRef<HTMLSpanElement>(null);
|
|
const [hasStarted, setHasStarted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) {
|
|
setHasStarted(true);
|
|
} else {
|
|
setHasStarted(false);
|
|
setCount(0);
|
|
}
|
|
},
|
|
{ threshold: 0.1 }
|
|
);
|
|
|
|
if (countRef.current) {
|
|
observer.observe(countRef.current);
|
|
}
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!hasStarted) return;
|
|
|
|
let startTimestamp: number | null = null;
|
|
let animationFrameId: number;
|
|
const step = (timestamp: number) => {
|
|
if (!startTimestamp) startTimestamp = timestamp;
|
|
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
|
setCount(Math.floor(progress * end));
|
|
if (progress < 1) {
|
|
animationFrameId = window.requestAnimationFrame(step);
|
|
}
|
|
};
|
|
animationFrameId = window.requestAnimationFrame(step);
|
|
return () => window.cancelAnimationFrame(animationFrameId);
|
|
}, [hasStarted, end, duration]);
|
|
|
|
return <span ref={countRef}>{count}</span>;
|
|
};
|
|
|
|
const counterItems = [
|
|
{
|
|
icon: "/assets/images/about/2/projects.webp",
|
|
count: 100,
|
|
suffix: "+",
|
|
text: "Projects Completed"
|
|
},
|
|
{
|
|
icon: "/assets/images/about/2/active.webp",
|
|
count: 50,
|
|
suffix: "+",
|
|
text: "Active Clients"
|
|
},
|
|
{
|
|
icon: "/assets/images/about/2/expert.webp",
|
|
count: 20,
|
|
suffix: "+",
|
|
text: "Expert People"
|
|
},
|
|
{
|
|
icon: "/assets/images/about/2/happy.webp",
|
|
count: 50,
|
|
suffix: "+",
|
|
text: "Happy Clients"
|
|
}
|
|
];
|
|
|
|
const CounterAreaThree: React.FC = () => {
|
|
return (
|
|
<section className="counter-area-three">
|
|
<div className="container">
|
|
<ul className="counter-area-three__list">
|
|
{counterItems.map((item, index) => (
|
|
<li key={index} className="counter-area-three__item count-box">
|
|
<div className="counter-area-three__icon">
|
|
<img src={item.icon} alt="icon" />
|
|
</div>
|
|
<div className="counter-area-three__content">
|
|
<h3 className="counter-area-three__count">
|
|
<Counter end={item.count} />
|
|
{item.suffix}
|
|
</h3>
|
|
<p className="counter-area-three__text">{item.text}</p>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default CounterAreaThree;
|