The nice thing to do when your website isn’t fully loaded is to play a little animation.
It lets the user know: “We’re working on it.”
“We know if it takes more than 2 seconds, you’ll leave forever.”
“My liege, we graciously offer you ~three blinking dots~.”
Animations act like a mantra. Like staring into a campfire. It hits your brain jingling keys hit a baby. Something primitive is opened inside, and we're transported to a place outside of time. And while we're there, no one notices the load…
Tutorial
In this blog, I try my hand at recreating various loaders I’ve seen on the web. In doing so, I attempt to make them as simple as possible, so they can easily be imported into your project or you can use the ideas to create your own.
Preliminary Junk
I set up a file structure that is an HTML file, index.html and a CSS file, index.css. The HTML looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>loaders</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="example-1" class="loader"></div>
<div id="example-2" class="loader">
<div id="bar-1" class="bar"></div>
<div id="bar-2" class="bar"></div>
<div id="bar-3" class="bar"></div>
</div>
<div id="example-3" class="loader"></div>
<div id="example-4" class="loader">
<div id="ball-container-1" class="ball-container">
<div id="ball-1" class="ball"></div>
</div>
<div id="ball-container-2" class="ball-container">
<div id="ball-2" class="ball"></div>
</div>
<div id="ball-container-3" class="ball-container">
<div id="ball-3" class="ball"></div>
</div>
</div>
</body>
</html>
I’ll explain each example in more detail when it’s relevant.
I set up some CSS variables for colors (from Coolors.co), flexbox, and margins for the general layout of the demo.
:root {
--main-bg-color: #1A535C;
--loader-bg-color: #FF6B6B;
--loader-acc-color: #4ECDC4;
}
body{
display: flex;
align-items: center;
justify-content: space-around;
background-color: var(--main-bg-color);
}
.loader{
margin-top: 5em;
}
Example 1
Example 1 has the HTML of one div, set up like this:
<div id="example-1" class="loader"></div>
Design: I give it the same size width and height and a border-radius: 50%;. By adding a border, you can see that this creates a circle.
I style the border-top-color with my accent color, var(--loader-acc-color). This overwrites the initial color in border for just the top of the border.
Animation: I set up @keyframes example-one so an element with this animation will rotate from 0 to 360 degrees.
I give the #example-1 element an animation property. Using the shorthand, I set the duration to 2s, iteration count to infinite, and name as example-one.
/* EXAMPLE 1 */
#example-1{
width: 3em;
height: 3em;
border-radius: 50%;
border: 0.75em solid var(--loader-bg-color);
border-top-color: var(--loader-acc-color); /* overrides top color to stand out */
animation: 2s infinite example-one;
}
@keyframes example-one{
from {transform: rotate(0deg)}
to {transform: rotate(360deg)}
}
Example 2
For Example 2, the HTML is a div container for three more divs. Each will be a “bar”.
<div id="example-2" class="loader">
<div id="bar-1" class="bar"></div>
<div id="bar-2" class="bar"></div>
<div id="bar-3" class="bar"></div>
</div>
Design: #example-2 is given some width, height and flexbox properties to center the bars within.
Each bar is given a margin, width, and starting height. I give them a background-color and some fancy border stuff for accent.
Animation: There are four parts to the example-two animation, divided into 0%, 25%, 50%, and 100%.
At 0%, the height is set to 2.5em, the same as the initial height of each bar. From 0% to 25%, the height grows to 5em. From 25% to 50%, it shrinks back to 2.5em where it will sustain until 100% when the animation restarts.
I give each bar an animation property with a duration of 1.5s, iteration count of infinite, and connect it to @keyframes by name, example-two.
Finally, in order to stagger the play, I grab the individual bars by their IDs. bar-1 gets a delay of 0.25s and bar-2 gets a delay of 0.5s.
/* EXAMPLE 2 */
#example-2{
width: 5em;
height: 5em;
display: flex;
justify-content: center;
align-items: center;
}
.bar{
margin: 0.2em;
width: 0.75em;
height: 2.5em;
border: 0.1em solid var(--loader-bg-color);
border-left: 0.1em solid var(--loader-acc-color);
background-color: var(--loader-bg-color);
animation: 1.5s infinite example-two;
}
#bar-2{animation-delay: 0.25s}
#bar-3{animation-delay: 0.5s}
@keyframes example-two{
0% {height: 2.5em}
25% {height: 5em}
50% {height: 2.5em}
100% {height: 2.5em}
}
Example 3
Example 3 is one div.
<div id="example-3" class="loader"></div>
Design: I give #example-3 a width: 5em; and height: 1em; to make it a long rectangle. I give it a background-color and some fancy border stuff for an accent.
Animation: I use the transform property again, but this time I flip the div from 0 to 180 degrees over its y axis using rotateY(). Then I flip it to 360 degrees, back to its starting position.
/* EXAMPLE 3 */
#example-3{
width: 5em;
height: 1em;
border: 0.3em solid var(--loader-bg-color);
border-right: 0.3em solid var(--loader-acc-color);
background-color: var(--loader-bg-color);
animation: 3s infinite example-three;
}
@keyframes example-three{
from { transform: rotateY(0deg);}
50% { transform: rotateY(180deg);}
to { transform: rotateY(360deg);}
}
Example 4
Example 4, most complex loader, has HTML of a container div with three children divs. Each child is also a container div for a single div that will be shaped like a ball.
<div id="example-4" class="loader">
<div id="ball-container-1" class="ball-container">
<div id="ball-1" class="ball"></div>
</div>
<div id="ball-container-2" class="ball-container">
<div id="ball-2" class="ball"></div>
</div>
<div id="ball-container-3" class="ball-container">
<div id="ball-3" class="ball"></div>
</div>
</div>
Design: The outer most container, #example-4 contains flexbox properties to center the loader within.
Each .ball-container gets the same width as height to make it a square and a margin-right to put some space in between.
Then, .ball-container gets flexbox properties to center the “ball” inside. This is important because as the ball changes sizes, I want it to remain centered.
Each .ball gets an initial width and height of 0. A border-radius of 50% turns them into circles, and a background-color makes them visible.
Animation: The animation follows the same logic as example-2 except I am manipulating each ball’s height and width.
From 0% to 20%, they grow from 0 x 0 to 1.5em x 1.5em. I keep them at this size from 20% to 40%. From 40% to 90% they shrink down to 0 x 0, and remain there from 90% to 100%.
I set each ball to have an animation property with a duration of 1.2s, iteration count of infinite, and name example-four.
Finally, I grab each ball by their individual ID so I can add an animation-delay to #ball-2 and #ball-3. This staggers the animation.
/* EXAMPLE 4 */
#example-4{
display: flex;
align-items: center;
justify-content:center;
}
.ball-container{
width: 1.5em;
height: 1.5em;
margin-right: 0.8em;
display: flex;
align-items: center;
justify-content:center;
}
.ball {
width: 0;
height: 0;
border-radius: 50%;
background-color: var(--loader-bg-color);
animation: 1.2s infinite example-four;
}
#ball-2{animation-delay: 0.1s;}
#ball-3{animation-delay: 0.2s;}
@keyframes example-four{
0% {
width: 0;
height: 0;
}
20% {
width: 1.5em;
height: 1.5em;
}
40%{
width: 1.5em;
height: 1.5em;
}
90%{
width: 0;
height: 0;
}
100%{
width: 0;
height: 0;
}
}
Conclusion
Thanks for reading the blog. I hope you found some of it useful.
At minimum, I hope one of my loaders transported you to that timeless place where — just for a moment — you felt the balance of the universe, tasted a slice of nirvana, inner peace. Best, Jason.





Top comments (1)
The simplest way to get a loader in your website/app is to use
<progress></progress>. No js, no svg animation and very efficient.