April 8, 2026/Simple/Loaders & Intros

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.

0
"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>
  );
}
  • gsap

Related Components

3W AGO
Willem Loading Animationloaders
3MO AGO
Full Orchestrated Load Sequenceloaders