SectionsSimpleApril 8, 2026
Counter Loading Screen
A numeric counter ticks from 0 to 100 during a loading screen using GSAP's object tweening, then the screen wipes away with a yPercent animation to reveal the page beneath.
View Full Demo →Preview
0
Source
index.jsx
"use client";
import { useEffect, useRef, useState } from "react";
import gsap from "gsap";
import styles from "./styles.module.css";
export default function CounterLoadingScreen({ duration = 2500 }) {
const [count, setCount] = useState(0);
const [done, setDone] = useState(false);
const [hidden, setHidden] = useState(false);
const screenRef = useRef(null);
const countRef = useRef({ value: 0 });
useEffect(() => {
const obj = countRef.current;
gsap.to(obj, {
value: 100,
duration: duration / 1000,
ease: "power1.inOut",
onUpdate() {
setCount(Math.floor(obj.value));
},
onComplete() {
setDone(true);
// Wipe the screen away
gsap.to(screenRef.current, {
yPercent: -100,
duration: 0.7,
ease: "power3.inOut",
delay: 0.1,
onComplete: () => setHidden(true),
});
},
});
}, [duration]);
if (hidden) {
return (
<div className={styles.revealed}>
<p className={styles.revealedText}>Page revealed</p>
<button
className={styles.resetBtn}
onClick={() => {
setCount(0);
setDone(false);
setHidden(false);
countRef.current.value = 0;
const obj = countRef.current;
gsap.to(obj, {
value: 100,
duration: duration / 1000,
ease: "power1.inOut",
onUpdate() { setCount(Math.floor(obj.value)); },
onComplete() {
setDone(true);
gsap.to(screenRef.current, {
yPercent: -100,
duration: 0.7,
ease: "power3.inOut",
delay: 0.1,
onComplete: () => setHidden(true),
});
},
});
}}
>
Replay
</button>
</div>
);
}
return (
<div className={styles.wrapper}>
<div ref={screenRef} className={styles.screen}>
<span className={styles.counter}>{count}</span>
<div
className={styles.progress}
style={{ transform: `scaleX(${count / 100})` }}
/>
</div>
</div>
);
}
styles.module.css
.wrapper {
position: relative;
height: 400px;
overflow: hidden;
background: #f5f5f5;
}
.screen {
position: absolute;
inset: 0;
background: #0d0d0d;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding: 2.5rem;
gap: 1.5rem;
}
.counter {
font-size: clamp(4rem, 15vw, 8rem);
font-weight: 700;
letter-spacing: -0.05em;
color: #ffffff;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.progress {
width: 100%;
height: 1px;
background: rgba(255, 255, 255, 0.4);
transform-origin: left;
transition: transform 50ms linear;
}
.revealed {
height: 400px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
background: #f5f5f5;
}
.revealedText {
font-size: 1rem;
color: #6b6b6b;
}
.resetBtn {
padding: 0.625rem 1.5rem;
font-size: 0.875rem;
font-weight: 500;
font-family: inherit;
color: #ffffff;
background: #1a1a1a;
border: none;
border-radius: 9999px;
cursor: pointer;
}
Dependencies
gsap