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?
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:
- Set
isLoading
initially astrue
- Fetch data from the source
- Generate cards for each of the fetched data items
- Use a simulated fetch that returns a Promise with a custom delay
- 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:
- Be a copycat (layout-wise): Let your skeletons mimic the layout and structure of the real content. It’s all about that authentic preview.
- Keep it light: Go easy on the animations; we’re aiming for smooth, not flashy. Light and breezy, my friend!
- 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! 🌐✨