The Magic Behind Skeleton Loading Animations

by kleamerkuri
The Magic Behind Skeleton Loading Animations.

Ever been stuck staring at a blank screen or twiddling your thumbs waiting for a website to load? We’ve all been there; we’ve all experienced the infinite loading spinner or a casual blank screen.

But guess what? Technology has improved (LOL)—we now have skeleton loading screens that at least showcase the layout of the site and create an illusion of speed.

Today’s post will walk you through creating a basic skeleton loading animation. We’ll simulate data fetch using JavaScript to recreate a past Font Awesome project as custom.

See the project in action on CodePen!

Note 👀
In the past project, a non-cached page might not display the Font Awesome icons. This is because views exceeded the free page views allowance (thank you guys!) and I can’t afford to pay for icons 🥲

In gist, we’re going to:

  • Create custom icons as replacements
  • Fetch custom icons from the host, adding additional delay to simulate long-fetch time
  • Use a skeleton loading animation while the fetch is in progress

Our waiting game woes are a tad aesthetically improved 😌

Meet the MVP: Skeleton Loading Animations

Now, imagine a world where, instead of the dreaded loading spinner, you get a sneak peek – a skeleton of what’s to come.

Enter skeleton loading animations, the unsung heroes of user experience. These nifty animations give you a visual heads-up that things are happening behind the scenes, turning the waiting game into a seamless, almost enjoyable experience.

Why skeletons, you ask? It’s all about simplicity – a wireframe or blueprint of your content, letting you know that the gears are turning.

It’s why skeleton animations are adopted by a vast majority of companies, big or small. Because who doesn’t love a good visual cue?

Citibank desktop skeleton loading animation.
Citibank desktop loading screen
Youtube desktop skeleton loading animation.
YouTube desktop loading screen

Skeletons give us that “something’s cooking” vibe, keeping us engaged and informed. It’s a bit like a sneak preview that reduces the “are we there yet” moments and transforms waiting time into a mini adventure.

Not to neglect the fact a good skeleton placeholder seamlessly transforms into the real deal, creating a smooth transition that keeps the frustration at bay and the good vibes flowing.

Behind the scenes of skeleton loading

Curious about the techy stuff?

Creating a skeleton loading animation is like strategically placing sneak-peek placeholders in your layout. They mimic the size and position of the actual content, making the transition from skeleton to real deal a breeze.

While libraries like React Content Loader and Shimmer offer ready-made components that you can sprinkle into your projects, this post is for those who want to create custom, from-scratch skeleton-loading animations 👷‍♀️

Restructuring the HTML

Starting with the HTML of our past project, we’ll strip all contents of the grid. This is to avoid the hard coding as well as to restructure each card since we’ll be using custom icons.

Our simplified HTML looks like this:

<h1>Skeleton Loading Animation</h1>
<div class="grid"></div>

Setting up the skeleton styles

I’ll reuse all of the styles of the existing project—the only modifications involve deleting the custom Font Awesome styles and adding the skeleton animation ones.

First, I’ll define some skeleton styles in the :root to inject conditionally when the fetch is in progress.

:root {
  --sk-border-color: #a7a7a7;
  --sk-border: 1px solid var(--sk-border-color);
  --sk-bg-item: #cfcfcf;
  --sk-bg: #e6e6e6;
}

Second, apply the skeleton styles to the desired components.

.card.skeleton {
  animation: sk-border 1s linear infinite alternate,
    sk-main 1s linear infinite alternate;
}

.card.skeleton .icon_wrapper {
  width: 115px;
  height: 80px;
  margin: 0 auto 20px auto;
}

.card.skeleton p {
  width: 100px;
  height: 20px;
  margin: 0 auto;
}

.card.skeleton .icon_wrapper,
.card.skeleton p {
  border-radius: 5px;
  animation: sk-load 1s linear infinite alternate,
    sk-border 1s linear infinite alternate;
}

Third, define the animations. I’m going for a pulsing animation, though, the type is up to you (feel free to do the wave or have no animation).

To achieve part of the color change, use a hex-to-hsl converter tool as a helper. HSL plays on hue, saturation, and lightness.

@keyframes sk-load {
  0% {
    background: hsl(0, 0%, 80%);
  }
  100% {
    background: hsl(0, 0%, 95%);
  }
}

@keyframes sk-border {
  0% {
    border-color: hsl(0, 0%, 80%);
  }
  100% {
    border-color: hsl(0, 0%, 95%);
  }
}

@keyframes sk-main {
  0% {
    background: hsl(0, 0%, 95%);
  }
  100% {
    background: hsl(0, 0%, 99%);
  }
}

Displaying skeleton load on data fetch

Time to bring our skeleton load screens to life. I’ll start by defining some variables:

const grid = document.querySelector('.grid');
let isLoading = false;

let ANIMALS = [
  'fish', 
  'crow', 
  'otter', 
  'dog',
  'cat',
  'horse',
  'dove',
  'frog',
  'hippo'
];

I’m using isLoading as a flag variable to determine the display of the skeleton vs the loaded content.

Getting the data from the host

Time to grab the icons from the source. I opted to host my SVG icons on a GitHub repo so all I’ll be doing is fetching the data from the URL.

const getIcons = () => {
  let data = [];
  
  ANIMALS.forEach( a => {
    let animalData = {
      link: `https://thehelpfultipper.github.io/tht_icons/animals/ICONS-${a}.svg`,
      name: a
    };
    
    data.push(animalData);
  });
  
  return data;
}

Notice how I’m modifying the fetched data to include the link and name properties for each icon. This is my desired data structure since we’ll display both icons and names on each card.

Looking to modify data structures? Check out How To Make A Really Good Shopping Cart With React

Dynamically creating each card

There’s no hard coding in this project—each card is added to the grid container dynamically.

const createCard = (c) => {
  // @c => { link, name }
  let { link, name } = c;
  
  const div = document.createElement('div');
  const iconDiv = document.createElement('div');
  const i = document.createElement('img');
  const p = document.createElement('p');
    
  div.classList.add('card', isLoading && 'skeleton');
  iconDiv.classList.add('icon_wrapper');
  i.src = link;
  p.innerText = isLoading ? '' : name.charAt(0).toUpperCase() + name.slice(1);
  
  !isLoading && iconDiv.appendChild(i);
  [iconDiv, p].forEach( item => div.appendChild(item));
  grid.appendChild(div);
}

I’m using isLoading throughout createCard to add the skeleton styles conditionally.

Displaying the skeleton load on data fetch

The general steps for displaying the skeleton load are:

  1. Set isLoading initially as true
  2. Fetch data from the source
  3. Generate cards for each of the fetched data items
  4. Use a simulated fetch that returns a Promise with a custom delay
  5. Render the skeleton components while data fetches
const simulateFetch = (data, delay) => {
  return new Promise( (res, rej) => {
    setTimeout( () => {
      res(data);
    }, delay);
  });
};

const clearSkeletonCards = cards => {
  cards.forEach( card => card.style.display = 'none');
}

let delay = 1800; // in milliseconds

( async () => {
  isLoading = true;
  let iconsData = getIcons();
  iconsData.forEach( icon => {
      createCard(icon);
    });
  
  let cards = document.querySelectorAll('.skeleton');
  
  let getData = await simulateFetch(iconsData, delay);
  
  if(getData.length > 0) {
    clearSkeletonCards(cards);
    isLoading = false;
    getData.forEach( icon => {
      createCard(icon);
    });
    
    document.querySelectorAll('.card').forEach( card => {
      if(card.classList.contains(false)) card.classList.remove(false);
    })
  } 
})();

There are a few things I’m doing in terms of cleanup:

  • Clearing the skeleton cards once data is fetched and ready
  • Update isLoading appropriately
  • Cleanup the boolean that’s inadvertently added as a class due to the conditional check 😬

Hey!
This is a simple demo for how to build a skeleton loading animation. If you’re working on a more robust application, you will not want to clear your UI and replace with a near-equivalent payload. Instead, you’ll want to create a custom, flexible skeleton component that’ll be functional and performative 💁‍♀️

Wrapping up with best practices

Here are some quick tips to keep in mind:

  1. Be a copycat (layout-wise): Let your skeletons mimic the layout and structure of the real content. It’s all about that authentic preview.
  2. Keep it light: Go easy on the animations; we’re aiming for smooth, not flashy. Light and breezy, my friend!
  3. Consistency is queen: Stick to a consistent design language for your skeletons. Familiarity breeds delight.

So, next time you’re faced with the loading time challenge, remember the magic of skeletons.

Let’s keep our digital adventures exciting – one graceful loading animation at a time! 🌐✨

Related Posts