How to Easily Create an Animated Counter Loading Screen with React-Spring

Caden Chen
Stackademic
Published in
7 min readApr 16, 2024

--

Recently, I came across a package that animates a counting sequence in React. I thought to myself that it would be a perfect fit for a loading screen.

The package is called react-spring. According to the documentation, “react-spring is a spring-physics-based animation library that should cover most of your UI-related animation needs.”

Scroll-to-view counter animation I built for my client with react-spring
Tesla’s animated number counter
Loading page from DesigneCraft that utilises react-spring

If you observe the animations closely, you’ll notice the numbers have a ‘spring-like’ feel. This distinctive motion is precisely why react-spring is so widely used. It allows developers to create animations that not only look smooth and natural but also feel interactive and responsive to user inputs. React-spring leverages physics-based animations to provide a more realistic and engaging user experience. This approach contrasts sharply with traditional animation techniques, which can often appear linear and artificial.

In this article, we will be recreating the loading page from DesigneCraft shown above.

Prerequisites

Before we get started, ensure you have the following:

  • A React project set up.
  • Tailwind dependencies installed npm install -D tailwindcss postcss autoprefixer && npx tailwindcss init -p
  • react-spring Package installed npm install @react-spring/web
  • Have a look at the previous article on Creating a Loading Page with React and Tailwind CSS
The files used in this article

Implementing the Loading Screen

Step 1: Create a home page

First, we need to establish a home or hero page that appears after the loading screen. This serves as the base page of the website and is typically the first content a user sees. We will do a simple one.

Page1.jsx

function Page1(props) {
return (
<div className={`relative min-h-screen flex bg-black`}>
<div className="container max-w-screen-xl mx-auto flex justify-center items-center text-4xl text-white">
Page 1
</div>
</div>
);
}

export default Page1;

Step 2: Create the loading page

Loading.jsx

import { useSpring, animated } from '@react-spring/web';

function Loading(props) {
const countAnimation = useSpring({
number: 100,
from: { number: 0 },
config: {
duration: 4800,
}
});

return (
<animated.div className="fixed top-0 left-0 w-full h-full flex justify-center items-center z-50 bg-black">
<animated.div className="text-white text-9xl font-bold">
{countAnimation.number.to(val => `${Math.floor(val)}%`)}
</animated.div>
</animated.div>
);
}

export default Loading;

Explanation:

Package Imports

import { useSpring, animated } from '@react-spring/web';

useSpring: This is a React hook from the react-spring library that is used to create spring animations. It allows you to define how properties should animate over time using spring physics.

animated: This is a special component or primitive from react-spring that can animate properties like style and other attributes.

React-Spring Animation

const countAnimation = useSpring({
number: 100,
from: { number: 0 },
config: { duration: 4800 }
});

useSpring configuration:

  • number: This specifies the target value for the animation. In this case, the animation will progressively count up to 100.
  • from: Indicates the starting state of the animation. Here, the counting starts from 0.
  • config: Defines the configuration options for the animation. The duration is set to 4800 milliseconds (4.8 seconds), dictating that the counting animation from 0 to 100 will take this amount of time to complete, providing a smooth visual transition for users.

Step 3: Merging the Loading Page with the Home Page

The primary function of a loading page is to initially cover the front page and then disappear after a certain period, revealing the main content. Implementing this effectively requires attention to several key details:

1. Animating an Exit Transition for the Loading Page

  • Fade Out Opacity: The loading page should smoothly transition from fully visible to completely transparent. This is managed by animating the opacity of the loading page from 1 to 0. However, even after the opacity reaches 0, the loading page element still physically covers the home page, potentially blocking interactions.

2. Ensuring Interactivity with the Home Page

  • Pointer Events: To resolve the issue of the loading page blocking the home page, even when invisible, the CSS property pointer-events: none; is used. This CSS rule is applied to the loading page once the fade animation is complete, ensuring that all mouse events (like clicks) pass through the loading page and interact directly with the elements on the home page underneath.

3. Managing Page Scrollability

  • Prevent Scrolling: While the loading screen is active, scrolling on the page should be disabled to keep the focus on the loading animation and to ensure a smooth transition to the home page. This can be achieved by adding a class to the body tag that sets overflow: hidden;.
  • Re-enabling Scrolling: Once the loading animation completes and the page is ready to transition to the home page, scrolling must be re-enabled by removing the overflow: hidden; style from the body. This change is typically tied to the same timer or event that controls the fade-out of the loading page.

Update your Loading.jsx

import { useSpring, animated } from '@react-spring/web';
import React, { useEffect, useState } from 'react';

function Loading(props) {
const countAnimation = useSpring({
number: 100,
from: { number: 0 },
config: {
duration: 4800,
}
});

const fadeAnimation = useSpring({ opacity: countAnimation.number.to(n => (n === 100 ? 0 : 1)), from: { opacity: 1 }, config: { duration: 1000 } });

const [scrollEnabled, setScrollEnabled] = useState(false);

useEffect(() => {
// Disable scrolling on mount
document.body.classList.add('no-scroll');

const timeout = setTimeout(() => {
// Enable scrolling after 5 seconds
document.body.classList.remove('no-scroll');
setScrollEnabled(true);
}, 5000);

return () => {
// Enable scrolling on unmount and clear timeout
document.body.classList.remove('no-scroll');
clearTimeout(timeout);
};
}, []);

return (
<animated.div className="fixed h-screen bg-black top-0 left-0 w-full h-full flex justify-center items-center z-50" style={{ ...fadeAnimation, pointerEvents: 'none' }}>

<animated.div className=" text-white text-9xl font-bold" style={{ zIndex: 9999 }}>
{countAnimation.number.to(val => `${Math.floor(val)}%`)}
</animated.div>
</animated.div>
);
}

export default Loading;

This refined explanation and implementation details should help guide you through setting up a seamless transition from a loading page to a home page, maintaining both aesthetics and functionality.

Step 4: Adding Advance Features

Let’s enhance our loading screen by incorporating spring animations and an animated loading decoration. This will make the loading experience more dynamic and visually engaging.

Use any image / video of your choice

LoadingScreenAdvance.jsx

import React, { useEffect, useState } from 'react';
import { useSpring, animated } from '@react-spring/web';
import box from '/boxloading.gif';

function LoadingScreenAdvance(props) {
const countAnimation = useSpring({
number: 100,
from: { number: 0 },
config: {
duration: 4600,
easing: t => 1 - Math.pow(1 - t, 3) // Start slower easing function
}
});

const fadeAnimation = useSpring({ opacity: countAnimation.number.to(n => (n === 100 ? 0 : 1)), from: { opacity: 1 }, config: { duration: 300 } });

const [scrollEnabled, setScrollEnabled] = useState(false);

useEffect(() => {
// Disable scrolling on mount
document.body.classList.add('no-scroll');

const timeout = setTimeout(() => {
// Enable scrolling after 5 seconds
document.body.classList.remove('no-scroll');
setScrollEnabled(true);
}, 5000);

return () => {
// Enable scrolling on unmount and clear timeout
document.body.classList.remove('no-scroll');
clearTimeout(timeout);
};
}, []);

return (
<animated.div className="fixed bg-backgroundColorPrimary h-screen top-0 left-0 w-full h-full flex justify-center items-center z-50" style={{ ...fadeAnimation, pointerEvents: 'none' }}>
<div className="p-4 rounded-md">
<div className="flex justify-center">
<animated.img src={box} alt="Boxes" style={{ width: '50%', height: 'auto' }} />
</div>
</div>
<animated.div className="fixed bottom-0 left-0 p-2 text-emerald-200 text-9xl font-bold" style={{ zIndex: 9999 }}>
{countAnimation.number.to(val => `${Math.floor(val)}%`)}
</animated.div>
</animated.div>
);
}

export default LoadingScreenAdvance;

Through the four detailed steps outlined in this guide, you are now equipped with the knowledge needed to design visually appealing and functional loading screens. Whether you’re implementing subtle animations that convey progress or adding decorative elements that capture the user’s attention, these techniques will help you create loading screens that are both beautiful and effective.

Remember, the goal is to enhance users’ experiences from the very first moment, making a lasting impression that encourages continued interaction with your site.

Check out my GitHub to download the component in full.

Stackademic 🎓

Thank you for reading until the end. Before you go:

--

--

A collection of React, Tailwind and Framer Motion features. Sometimes I write about Figma and Jitter too.