How to Use Pure CSS to Create a Beautiful Loading Animation for your App

Colby FayockColby Fayock
10 min read

If you've been around the internet lately, you've most likely seen a nice subtle loading animation that fills page content before gracefully loading in.

Some of the social giants like Facebook even use this approach to give page loading a better experience. How can we do that with just some simple CSS?

What are we going to build?

We're going to create a loading animation using a CSS class that you can apply to pretty much any element you want (within reason).

Image Loading animation preview

This gives you great flexibility to use it and makes the solution nice and simple with only CSS.

While the snippet is pretty small and you could just copy and paste it, I'll walk you through what's happening and an example of using it dynamically when loading data.

Just want the snippet?

You can grab it here!

Do I need to know how to animate before this tutorial?

No! We'll walk through in detail exactly what you need to do. In fact, the animation in this tutorial is relatively simple, so let's dig in!

Part 1: Creating our loading animation

This first part is going to focus on getting the loading animation together and seeing it on a static HTML website. The goal is to walk through actually creating the snippet. We'll only use HTML and CSS for this part.

Step 1: Creating some sample content

To get started, we'll want a little sample content. There's really no restrictions here, you can create this with basic HTML and CSS or you can add this to your Create React App!

For the walk through, I'm going to use HTML and CSS with a few examples of content that will allow us to see this in effect.

To get started, create a new HTML file. Inside that HTML file, fill it with some content that will give us the ability to play with our animation. I'm going to use fillerama which uses lines from my favorite TV show Futurama!

Image Static HTML & CSS webpage with content from fillerama.io

If you're going to follow along with me, here's what my project looks like:

my-css-loading-animation-static
- index.html
- main.css

Follow along with the commit!

Step 2: Starting with a foundation loading class

For our foundation, let's create a new CSS class. Inside our CSS file, let's add:

.loading {
  background: #eceff1;
}

With that class, let's add it to a few or all of our elements. I added it to a few paragraphs, headings, and lists.

<p class="loading">For example...

Image Static HTML & CSS webpage with a gray background for the content

That gives us a basic background, but we'd probably want to hide that text. When it's loading, we won't have that text yet, so most likely we would want to use filler text or a fixed height. Either way, we can set the color to transparent:

.loading {
  color: transparent;
  background: #eceff1;
}

Image Static HTML & CSS webpage with a gray background and transparent color for the content

If you notice with list elements, whether you apply the class to the top level list element (<ol> or <ul>) vs the list item itself (<li>), it looks like one big block. If we add a little margin to the bottom of all list elements, we can see a different in how they display:

li {
  margin-bottom: .5em;
}

Image Style difference between applying to the top level list or the list items

And now it's starting to come together, but it kind of just looks like placeholders. So let's animate this to look like it's actually loading.

Follow along with the commit!

Step 3: Styling and animating our loading class

Before actually animating our class, we need something to animate, so let's add a gradient to our .loading class:

.loading {
  color: transparent;
  background: linear-gradient(100deg, #eceff1 30%, #f6f7f8 50%, #eceff1 70%);
}

This is saying that we want a linear gradient that's tilted at 100 degrees, where we start with #eceff1 and fade to #f6f7f8 at 30% and back to #eceff1 at 70%;

Image Subtle gradient background that might look like a glare

It's hard to see initially when it's still, it might just look like a glare on your computer! If you'd like to see it before moving on, feel free to play with the colors above to see the gradient.

Now that we have something to animate, we'll first need to create a keyframes rule:

@keyframes loading {
  0% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0 50%;
  }
}

This rule when applied will change the background position from starting at 100% of the x-axis to 0% of the x-axis.

With the rule, we can add our animation property to our .loading class:

.loading {
  color: transparent;
  background: linear-gradient(100deg, #eceff1 30%, #f6f7f8 50%, #eceff1 70%);
  animation: loading 1.2s ease-in-out infinite;
}

Our animation line is setting the keyframe to loading, telling it to last for 1.2 seconds, setting the timing function to ease-in-out to make it smooth, and tell it to loop forever with infinite.

Image No change – it's not animating

If you notice though after saving that, it's still not doing anything. The reason for this is we're setting our gradient from one end of the DOM element to the other, so there's nowhere to move!

So let's try also setting a background-size on our .loading class.

.loading {
  color: transparent;
  background: linear-gradient(100deg, #eceff1 30%, #f6f7f8 50%, #eceff1 70%);
  background-size: 400%;
  animation: loading 1.2s ease-in-out infinite;
}

Now, since our background expands beyond our DOM element (you can't see that part), it has some space to animate with and we get our animation!

Image Our loading animation!

Follow along with the commit!

Part 2: Using our loading animation in a dynamic app

Now that we have our loading animation, let's put it into action with a basic example where we fake a loading state.

The trick with actually using this is typically we don't have the actual content available, so in most cases, we have to fake it.

To show you how we can do this, we're going to build a simple React app with Next.js.

Step 1: Creating an example React app with Next.js

Navigate to the directory you want to create your new project in and run:

yarn create next-app
# or
npm init next-app

It will prompt you with some options, particularly a name which will determine the directory the project is created in and the type of project. I'm using my-css-loading-animation-dynamic and the "Default Starter App".

Image Creating a new project with Next.js

Once installed, navigate into your new directory and start up your development server:

cd [directory]
yarn dev
# or 
npm run dev

Image Starting development server with Next.js

Next, let's replace the content in our pages/index.js file. I'm going to derive the content from the previous example, but we'll create it similar to how we might expect it to come from an API. First, let's add our content as an object above our return statement:

const content = {
  header: `So, how 'bout them Knicks?`,
  intro: `What are their names? I'm Santa Claus! This opera's as lousy as it is brilliant! Your lyrics lack subtlety. You can't just have your characters announce how they feel. That makes me feel angry! Good news, everyone! I've taught the toaster to feel love!`,
  list: [
    `Yes! In your face, Gandhi!`,
    `So I really am important? How I feel when I'm drunk is correct?`,
    `Who are those horrible orange men?`
  ]
}

To display that content, inside <main>, let's replace the content with:

<main>
  <h1>{ content.header }</h1>
  <p>{ content.intro }</p>
  <ul>
    { content.list.map((item, i) => {
      return (
        <li key={i}>{ item }</li>
      )
    })}
  </ul>
</main>

And for the styles, you can copy and paste everything from our Part 1 main.css file into the <style> tags at the bottom of our index page. That will leave us with:

Image Basic content with Next.js

With that, we should be back to a similar point we finished at in Part 1 except we're not actively using any of the loading animations yet.

Follow along with the commit!

Step 2: Faking loading data from an API

The example we're working with is pretty simple. You'd probably see this coming pre-generated statically, but this helps us create a realistic demo that we can test our loading animation with.

To fake our loading state, we're going to use React's useState, useEffect, and an old fashioned setTimeout to preload some "loading" content, and after the setTimeout finishes, update that content with our actual data. In the meantime, we'll know that we're in a loading state with a separate instance of useState.

First, we need to import our dependencies. At the top of our pages/index.js file, add:

import { useState, useEffect } from 'react';

Above our content object, let's add some state:

const [loadingState, updateLoadingState] = useState(true);
const [contentState, updateContentState] = useState({})

And in our content, we can update the instances to use that state:

<h1>{ contentState.header }</h1>
<p>{ contentState.intro }</p>
<ul>
  { contentState.list.map((item, i) => {
    return (
      <li key={i}>{ item }</li>
    )
  })}
</ul>

Once you save and load that, you'll first notice we get an error because our list property doesn't exist on our contentState, so we can first fix that:

{ Array.isArray(contentState.list) && contentState.list.map((item, i) => {
  return (
    <li key={i}>{ item }</li>
  )
})}

And after that's ready, let's add our setTimeout inside of a useEffect hook to simulate our data loading. Add this under our content object:

useEffect(() => {
  setTimeout(() => {
    updateContentState(content);
    updateLoadingState(false)
  }, 2000);
}, [])

Once you save and open up your browser, you'll notice that for 2 seconds you don't have any content and then it loads in, basically simulating loading that data asynchronously.

Follow along with the commit!

Step 3: Adding our loading animation

Now we can finally add our loading animation. So to do this, we're going to use our loading state we set up using useState and if the content is loading, add our .loading class to our elements.

Before we do that, instead of individually adding this class to each item in the DOM, it might make more sense to do so using CSS and adding the class to the parent, so let's do that first.

First, update the .loading class to target our elements:

.loading h1,
.loading p,
.loading li {
  color: transparent;
  background: linear-gradient(100deg, #eceff1 30%, #f6f7f8 50%, #eceff1 70%);
  background-size: 400%;
  animation: loading 1.2s ease-in-out infinite;
}

Then we can dynamically add our class to our <main> tag:

<main className={loadingState ? 'loading' : ''}>

Note: if you use Sass, you can manage your loading styles by extending the .loading class in the instances you want to use it or create a placeholder and extend that!

And if you refresh the page, you'll notice it's still just a blank page for 2 seconds!

The issue, is when we load our content, nothing exists inside of our tags that can that would allow the line-height of the elements to give it a height.

Image No height when there's no content

But we can fix that! Because our .loading class sets our text to transparent, we can simply add the word Loading for each piece of content:

const [contentState, updateContentState] = useState({
  header: 'Loading',
  intro: 'Loading',
  list: [
    'Loading',
    'Loading',
    'Loading'
  ]
})

Note: We can't use an empty space here because that alone won't provide us with a height when rendered in the DOM.

And once you save and reload the page, our first 2 seconds will have a loading state that reflects our content!

Image HTML & CSS loading animation

Follow along with the commit!

Some additional thoughts

This technique can be used pretty broadly. Being a CSS class makes it nice and easy to add where every you want.

If you're not a fan of setting the Loading text for the loading state, another option is to set a fixed height. The only issue with that is it requires more maintenance for tweaking the CSS to match what the content loading in will look like.

Additionally, this won't be perfect. More often than not, you won't know exactly how much copy you have on a page. The goal is to simulate and hint that there will be content and that it's currently loading.

What's your favorite loading animation?

Let me know on Twitter!

Join in on the conversation!

0
Subscribe to my newsletter

Read articles from Colby Fayock directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Colby Fayock
Colby Fayock

Director of DevX Engineering @ Cloudinary