Skip to content

Commit 1b909ab

Browse files
committed
Added the rolling numbers effect
1 parent 944a36e commit 1b909ab

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { useEffect, useRef, useState } from "react";
2+
3+
interface CounterProps {
4+
value: number;
5+
duration?: number; // animation duration in ms
6+
suffix?: string; // optional suffix like %, K+, etc.
7+
}
8+
9+
const Counter: React.FC<CounterProps> = ({ value, duration = 1500, suffix = "" }) => {
10+
const [count, setCount] = useState(0);
11+
const [hasAnimated, setHasAnimated] = useState(false);
12+
const ref = useRef<HTMLDivElement | null>(null);
13+
14+
useEffect(() => {
15+
if (!ref.current) return;
16+
17+
const observer = new IntersectionObserver(
18+
(entries) => {
19+
const entry = entries[0];
20+
if (entry.isIntersecting && !hasAnimated) {
21+
animateCount();
22+
setHasAnimated(true);
23+
}
24+
},
25+
{ threshold: 0.5 } // Trigger when 50% of element is visible
26+
);
27+
28+
observer.observe(ref.current);
29+
return () => observer.disconnect();
30+
}, [hasAnimated]);
31+
32+
const animateCount = () => {
33+
const startTime = performance.now();
34+
35+
const step = (now: number) => {
36+
const progress = Math.min((now - startTime) / duration, 1);
37+
const current = Math.floor(progress * value);
38+
setCount(current);
39+
40+
if (progress < 1) {
41+
requestAnimationFrame(step);
42+
}
43+
};
44+
45+
requestAnimationFrame(step);
46+
};
47+
48+
return (
49+
<div ref={ref}>
50+
{count.toLocaleString()}
51+
{suffix}
52+
</div>
53+
);
54+
};
55+
56+
export default Counter;

src/theme/Footer/Layout/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {type ReactNode, useState, useEffect} from 'react';
33
import Link from "@docusaurus/Link";
44
import type {Props} from '@theme/Footer/Layout';
55
import './enhanced-footer.css';
6+
import Counter from './Counter';
67

78
// Dynamic stats interface
89
interface FooterStats {
@@ -105,7 +106,9 @@ export default function FooterLayout({
105106
</svg>
106107
</div>
107108
<div className="stat-content">
108-
<div className="stat-number">{stats.activeUsers}</div>
109+
<div className="stat-number">
110+
<Counter value={parseInt(stats.activeUsers)} suffix="K+" />
111+
</div>
109112
<div className="stat-label">Active Learners</div>
110113
</div>
111114
</div>
@@ -117,7 +120,9 @@ export default function FooterLayout({
117120
</svg>
118121
</div>
119122
<div className="stat-content">
120-
<div className="stat-number">{stats.tutorials}</div>
123+
<div className="stat-number">
124+
<Counter value={parseInt(stats.tutorials)} suffix="+" />
125+
</div>
121126
<div className="stat-label">Tutorials</div>
122127
</div>
123128
</div>
@@ -129,7 +134,9 @@ export default function FooterLayout({
129134
</svg>
130135
</div>
131136
<div className="stat-content">
132-
<div className="stat-number">{stats.successRate}</div>
137+
<div className="stat-number">
138+
<Counter value={parseInt(stats.successRate)} suffix="%" />
139+
</div>
133140
<div className="stat-label">Success Rate</div>
134141
</div>
135142
</div>

0 commit comments

Comments
 (0)