Say "No" to loading spinner - How to create a creative loading component with 99.1% pure CSS
Tags: webdev, tutorial, css, frontend
Published: 2023-10-25 21:02:49

When you're working on the front-end of your app and its routes, you'll likely encounter a UX issue: loading components. Loading components are a screen feature commonly used to display content to users while the requested data has not yet been fully loaded. This feature makes the response time of the request go unnoticed by the user, reducing their frustration when using the application and thus directly impacting the user conversion rate.

## Generally, Generic, Gattlestar Balatica
![borinnggggg](https://media.tenor.com/raULr5MBzJ4AAAAd/no-boring.gif)There are several ways to create a loading component, but developers usually choose to venture into familiar territory: spinners. The reason why this happens is clear. Spinners solve the issue with requests, there are thousands of videos on how to make them, and, the main reason, there are already many libraries that provide them ready to use. So, what's the problem? Any problems. As they are quite common, spinners end up becoming a generic solution for a common problem. They do solve the issue, but where's the magic in that?

So, why use spinners when you can use your brand on the loading screen?

## Your brand on the loading screen

Apart from being really cool, placing your brand logo on the loading screen reinforces to the user that they are in your application. Additionally, seeing something different from the usual sparks the user's curiosity, and they think, "Wow, that's cool!".

In this article, you'll find a tutorial on creating a creative loading components using CSS.

## Requirements

In this tutorial, I used these technologies:
- Next.js
- CSS

Next.js was used due to how this framework handle routes. Simple and easy peasy. The focus of this article is on CSS, so you can easily adapt it to any technology you want. Finally, grab your keyboard and let's get started.

## Step 1 - Create pages

For this tutorial, three very basic pages were created just to simulate the functioning of the loading component. The idea for the app theme came from the series "Breaking Bad", which, by the way, is my favorite. Additionally, the application was implemented in a gamified manner.

I won't go into much detail about the creation of the screens themselves, but I'll provide the pages JSX code and the corresponding CSS. But, in any case, I'll leave the project repository available at the end of this article.

### Home (/)
```JSX
import Image from "next/image";
import {redirect} from "next/navigation"
import styles from "./page.module.css";


import chibiBerg from "../assets/chibiberg.png";
import Link from "next/link";


export default function Home() {
  return (
    <div className={styles.container}>
      <div className={styles.contentWrapper}>
        <div className={styles.dialogBox}>
          <p className={styles.message}>Say my name!</p>
        </div>
        <div className={styles.imageWrapper}>
          <Image src={chibiBerg} alt="Bad and problematic person" />
        </div>
      </div>
      <div className={styles.buttonWrapper}>
        <Link className={styles.buttonOff} href={"/walter-white"}>Walter White</Link>
        <Link className={styles.buttonOn} href={"/heisenberg"} >Heisenberg</Link>
      </div>
    </div>
  );
}
```

### Walter White (/walter-white) - wrong answer
```JSX
import Image from "next/image";
import Link from "next/link";
import styles from "@/app/page.module.css";

import chibiBerg from "@/assets/chibiberg.png";

export default function WalterWhite() {
  return (
    <div className={styles.container}>
      <div className={styles.contentWrapper}>
        <div className={styles.dialogBox}>
          <p className={styles.message}>SAY.. MY.. NAME!</p>
        </div>
        <div className={styles.imageWrapper}>
          <Image src={chibiBerg} alt="Bad and problematic person" />
        </div>
      </div>
      <Link className={styles.buttonOn} href={"/heisenberg"} >Heisenberg</Link>
    </div>
  );
}
```

### Heisenberg (/heisenberg) - goddamm right answer
```JSX
import Image from "next/image";
import styles from "@/app/page.module.css";

import chibiBerg from "@/assets/chibiberg.png";

export default function Heisenberg() {
  return (
    <div className={styles.container}>
      <div className={styles.contentWrapper}>
        <div className={styles.dialogBox}>
          <p className={styles.message}>{"You're goddamm right!"}</p>
        </div>
        <div className={styles.imageWrapper}>
          <Image src={chibiBerg} alt="Bad and problematic person" />
        </div>
      </div>
    </div>
  );
}
```

### CSS
```CSS
.container {
  min-height: 100vh;
  width: 100%;
  background-color: #515151;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 6rem;
}

.contentWrapper {
  display: flex;
  align-items: flex-start;
}

.imageWrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22rem;
  width: 16rem;
  overflow: hidden;
}

.dialogBox {
  display: flex;
  padding: 2rem 4rem;
  background-color: white;
  border: 1px solid transparent;
  border-radius: 1.5rem 1.5rem 0 1.5rem;
}

.message {
  font-weight: 700;
  font-size: 2rem;
}

.buttonWrapper {
  display: flex;
  gap: 1rem;
}

.buttonOn, .buttonOff {
  display: inline-block;
  color: #000000;
  padding: 1rem 4rem;
  border: 1px solid transparent;
  border-radius: 1rem;
  cursor: pointer;
  font-size: 1.5rem;
  font-weight: 700;
  transition: .2s;

}

.buttonOn {
  background-color: #519bc0;
}

.buttonOff {
  background: transparent;
}

.buttonOn:hover{
  background-color: #81cdff;
}

.buttonOff:hover {
  background-color: #9f0000;
  color: #ffffff;
  opacity: .8;
}
```

## Step 2 - Create loading component
![loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9h4z38bb2z7f5gnz85i.gif)Now, let's get to the good part. I'll divide this step into two parts. First, I'll show you how to create the logo animation, and then I'll demonstrate the text animation. So, let's do this.

### Breaking Bad logo
To obtain the Breaking Bad logo as a component, I used the [SVGR](https://react-svgr.com/) tool, which converts your SVG code into a component in a very simple way. Just copy the code into the [SVGR playground](https://react-svgr.com/playground/), and you're all set. I chose this approach because the img tag or Image (Next.js) severely limits SVG manipulation. So, if you need to make any color changes to an SVG, for example, this is a good approach.
![SVGR playground](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmfeug1btykcnyl7ijg2.png)


### Loading component with only logo
```JSX
import Icon from "../Icon";
import styles from "./index.module.css";

export default function Loading() {
  return (
    <main className={styles.main}>
      <div className={styles.logoWrapper}>
        <Icon /> {/* Back logo */}
        <div className={styles.frontLogoWrapper}>
          <Icon /> {/* Front logo */}
        </div>
      </div>
    </main>
  );
}
```
```CSS
.main {
  /* We only want the component to appear*/
  position: fixed;
  top: 0;
  left: 0;
  min-height: 100%;
  width: 100%;
  background-color: #eacea7;

  /* Center the loading component */
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  gap: 5rem;

  z-index: 10; /* High z-index to cover all app */
}

.logoWrapper {
  position: relative; /* Define which element the SVG is relative to.*/
}

.logoWrapper svg {
  width: 30rem; /* Both icons should have the same dimensions. */
}

.logoWrapper > svg {
  fill: #aaaaaa; /* Change back logo color */
}

.frontLogoWrapper > svg {
  fill: #30693e; /* Change front logo color */
}


.frontLogoWrapper {
  /* Position the logo in front of the other one. */
  position: absolute;
  top: 0;
  left: 0;

  /* And here's the magic */
  overflow: hidden;
  animation: slide 3s ease-out forwards; /* "forwards" so that the
  wrapper width stays the same when the animation is over. */
}
@keyframes slide {
  0% {
    width: 0%;
  }

  100% {
    width: 100%;
  }
}
```

#### In summary,
  1. Wrap the front logo in a div tag
  2. Position the logo in front of the other one.
  3. Overflow hidden to cover the front logo
  4. Animate the wrapper to increase its width and uncover the
     front logo

### Loading component with logo and text animation
```JSX
import Icon from "../Icon";
import styles from "./index.module.css";

export default function Loading() {
  return (
    <main className={styles.main}>
      <div className={styles.logoWrapper}>
        <Icon />
        <div className={styles.frontLogoWrapper}>
          <Icon />
        </div>
      </div>
      <div className={styles.textWrapper}>
        <p>Knock knock...</p>
        <p>{"Who's there..?"}</p>
        <p>The DANGER!</p>
      </div>
    </main>
  );
}
```
```CSS
/* Same logo CSS file  */

.textWrapper {
  /* Define which element the p tag is relative to.*/
  position: relative;
  width: 30rem;
  height: 3rem;
  overflow: hidden; /* hides texts out of the div */
  margin-top: 2.5rem;
}

.textWrapper p {
  position: absolute;
  text-align: center;
  width: 100%;
  color: #30693e;
  font-weight: 700;
  font-size: 2rem;
}

/*
  showOut - slide from center to top and cover the text
  showIn  - slide from bottom to center and uncover the text
*/

/*Knock-knock - stays 600ms in screen*/
.textWrapper p:nth-child(1) {
  animation: showOut 300ms ease-out 600ms forwards;
}

/*Who's there..? - enter after 600ms and stays 1.4s (2s - 600ms) in screen*/
.textWrapper p:nth-child(2) {
  top: 3rem;
  animation: showIn 300ms ease-out 600ms forwards,
    showOut 300ms ease-out 2s forwards;
}

/*The DANGER! - enter after 2s and stays 1s in screen*/
.textWrapper p:nth-child(3) {
  top: 3rem;
  animation: showIn 300ms ease-out 2s forwards;
}

@keyframes showIn {
  from {
    top: 3rem;
  }

  to {
    top: 0rem;
  }
}

@keyframes showOut {
  from {
    top: 0;
  }

  to {
    top: -3rem;
  }
}
```
#### In summary,
  1. Wrap the text in a div tag with relative position
  2. Position as absolute the texts.
  3. Overflow hidden to cover the texts
  4. Animate each text to show and cover subsequently

## Step 3 - Let's put the pieces together

In Next.js 13, we have a really cool feature called [route groups](https://nextjs.org/docs/app/building-your-application/routing/route-groups). One of its many possibilities is that it allows the developer to define a unique layout for pages inside the group. Knowing this, here we'll use this approach to render the loading component before selected pages in just one place. Additionally, we'll use `useEffect()` and `useState()`, which are hooks from the React library, with `setTimeOut()` to simulate request response time. These three together allow us to schedule the loading state change after a while and to make sure it happens only once.

### Route group example
![route group example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdox9hy9f5mwk5gkivix.png)
### Layout
```JSX
"use client"

import Loading from "@/components/Loading";
import { useEffect, useState } from "react";

export default function Layout({
    children,
  }: {
    children: React.ReactNode
  }) {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setTimeout(() => setLoading(false), 3000);
  }, []);

  return loading ? <Loading /> : <>{children}</>;
}
```

## Final result

You can fully access the application [here](https://loading-example-devto.vercel.app/
).
![app working](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbprtzhdgzk31x2y9oqk.gif)

## Conclusion

So... The idea to create this tutorial came from an encouragement from my tech lead [Jessica](https://www.linkedin.com/in/jessilyneh/) when I had developed a loading component for the voluntary project known as [Lacrei Saúde](https://www.linkedin.com/company/lacrei-saude/mycompany/verification/). Additionally, I would like to extend my thanks to [Daniel](https://www.linkedin.com/in/daniel-bertolini-correia-09b2ba118/) for reviewing this article and suggesting some improvements. As promised, I'm leaving the application [repository here](https://github.com/apocsenpai/loading-example-devto), and I also have a question: Have you had water today?