Creating a Loading Page with React and Tailwind CSS

Caden Chen
5 min readFeb 25, 2024

In modern web development, delivering a seamless user experience is paramount. Visual feedback during loading intervals plays a pivotal role in maintaining user engagement and satisfaction. Loading pages serve not only to inform users about ongoing processes but also to keep them engaged while awaiting content. In this tutorial, we’ll delve into crafting an aesthetically pleasing loading page using Tailwind CSS and React, enriched with captivating animations to elevate the overall user experience.

Image from Lia | Yusrilia Design

Prerequisites

Before we get started, ensure you have the following:

  • Basic understanding of React.
  • Node.js and npm installed on your machine.
  • A React project set up.
  • Tailwind dependencies installed
  • Framer Motion installed npm install framer-motion

This medium article provides a quick way to set up React and Tailwind.

Once the environment is set up, we can proceed.

The files used in this article

App.jsx

import Page1 from "./components/Page1.jsx";
import LoaderSimple from "./components/LoaderSimple.jsx";

export default function App() {
return (
<>
<LoaderSimple />
<Page1 />
</>
);
}

Implementing a Loading Page

Step 1: Create a simple webpage

Page1.jsx

const background = "bg-blue";

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 a loading page

LoaderSimple.jsx

import { motion } from 'framer-motion';

function LoaderSimple(props) {

return (
<div className="fixed bg-black h-screen top-0 left-0 w-full h-full flex justify-center items-center z-10">
<div className="p-4 rounded-md">
<div className="flex justify-center">
<>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: Infinity }
}}
/>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: Infinity, delay: 0.2 }
}}
/>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: Infinity, delay: 0.4 }
}}
/>
</>
</div>
</div>
</div>
);
}

export default LoaderSimple;

Explanation:

  1. Packages

import { motion } from 'framer-motion';: Imports the motion component from the Framer Motion library for creating animations.

2. Animation Configuration:

  • Each dot is animated using the animate prop provided by Framer Motion.
  • The animation specifies a vertical movement (y) from 0 to -20 and back to 0, simulating the dot's bouncing effect.
  • Additionally, the opacity of each dot gradually decreases from 1 to 0, creating a fading effect as the dot moves up.

3. Animation Duration and Repetition:

  • The animation duration is set to 1 second (transition: { duration: 1 }).
  • The animation repeats infinitely (repeat: Infinity) to create an ongoing looping effect.
  • The delay between each dot’s animation is staggered using the delay property to create a sequential effect.

Step 3: Merging the Two Components Together

To seamlessly transition from the loading page to the main webpage, I implemented a fade-out effect on the loading page and a fade-in effect on the main webpage.

Update your LoaderSimple.jsx

import React, { useEffect, useState } from 'react';
import { motion, useAnimation } from 'framer-motion'; // Import useAnimation hook

function LoaderSimple(props) {
const [loading, setLoading] = useState(true);
const controls = useAnimation(); // Use useAnimation hook

useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 4000);

return () => clearTimeout(timer);
}, []);

useEffect(() => {
if (loading) {
controls.start({
opacity: 1,
transition: { duration: 1 }
});
} else {
controls.start({
opacity: 0,
transition: { duration: 1 }
});
}
}, [loading, controls]);

return (
<motion.div
className="fixed bg-black h-screen top-0 left-0 w-full h-full flex justify-center items-center z-10"
animate={controls}
>
<div className="p-4 rounded-md">
<div className="flex justify-center">
<>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: 2 }
}}
/>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: 1.8, delay: 0.2 }
}}
/>
<motion.span
className="w-4 h-4 my-12 mx-1 bg-white rounded-full"
animate={{
y: [0, -20, 0],
opacity: [1, 0], // Fades out
transition: { duration: 1, repeat: 1.6, delay: 0.4 }
}}
/>
</>
</div>
</div>
</motion.div>
);
}

export default LoaderSimple;

Explanation:

  1. useState Hook: The component uses the useState hook to manage the state of the loading animation. Initially, the loading state is set to true, indicating that the loading animation should be displayed.
  2. useEffect Hook (Timer): Another useEffect hook is used to set a timer that changes the loading state to false after 4000 milliseconds (4 seconds). This simulates a loading period before the content is ready to be displayed.
  3. useEffect Hook (Controls): This useEffect hook controls the animation of the loading spinner. It starts the animation when the component mounts (loading is true) and fades out the loading spinner when the content is ready (loading is false). It utilizes the useAnimation hook from Framer Motion to control the animation.

And voilà! You’ve successfully created a loading page using React and Tailwind CSS.

--

--

Caden Chen

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