Creating an animated loading spinner with pure CSS

Adeyele Paul - Apr 8 - - Dev Community

You know that little loading spinner that shows up whenever a website is loading?

The one that spins around and around, almost hypnotizing you as you impatiently wait for the content to appear?

Yeah, those things. They may seem simple, but creating a smooth, mesmerizing loading animation like that with pure CSS is actually pretty darn cool.

I recently challenged myself to code up one of those animated spinners from scratch, using only CSS animations and styles - no JavaScript tricks allowed.

Why?

Because CSS animations are just plain fun to work with!

They allow you to bring some movement and life to your website components without having to lean on JavaScript.

Here's the final product:

the final product

Now, I'm not gonna lie - seeing the final product spin so effortlessly after getting it all coded up gave me a silly sense of accomplishment.

Maybe I need to get out more often?

In any case, I'm going to walk through exactly how I built this living spinner step-by-step.

Our HTML Spinner Markup

Before we can start coding our CSS, we need to lay out the basic HTML elements that will serve as the skeleton for our loading spinner.

Now, I'll be honest - this markup isn't going to win any awards for complexity. It's about as bare-bones as markup gets, consisting of just a few div containers:



<div class="spinner">
  <div class="spinner-text">Loading</div>
  <div class="spinner-sector spinner-sector-red"></div>
  <div class="spinner-sector spinner-sector-blue"></div> 
  <div class="spinner-sector spinner-sector-green"></div>
</div>


Enter fullscreen mode Exit fullscreen mode

See? Told you it was simple! We've got a parent .spinner div that will contain the entire spinner.

Inside that, we have:

  1. A div for our "Loading" text
  2. Three divs for each colored spinning sector

I've given each of the sector divs a secondary class name like .spinner-sector-red to easily identify and style them individually later on.

A little organization never hurt, right?

With our HTML building blocks ready, we can now jump into the fun part - styling and animating these divs into a spinning loading icon!

First up, let's deal with that .spinner parent container...

Centering and Sizing

Okay, now we want the .spinner parent container to be properly size, centered and contained to a perfect circle.

Here's how we'll make that happen:



* {
  box-sizing: border-box;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}

.spinner {
  width: 300px;
  height: 300px;
  position: relative;
}


Enter fullscreen mode Exit fullscreen mode

First up, we apply a global box-sizing: border-box style to all elements.

This makes our dimensions and padding/borders behave consistently across different elements.

Next, we target the body and apply some flex centering!

By setting display: flex, justify-content: center, and align-items: center...

...we make sure our .spinner (and anything else) is vertically and horizontally centered within the full viewport height of 100vh.

With the body flexbox styles in place, our .spinner element will be positioned right in the middle of the screen.

For the .spinner itself, we set an equal width and height of 300 pixels.

This determines the overall size of our circular spinner area. Adjust this value up or down if you want a bigger or smaller loading icon.

Finally, we make the .spinner position: relative. This seems innocent enough, but it's actually a crucial step. Those colored sector divs inside are going to be positioned absolutely within this relative parent.

So we've got our spinner all centered up and properly contained now.

But those inner divs are still unstyled. No spinning, no color, no animation - just some sad, boxed up html.

We need to make these elements!

Shaping the Sectors: Circles, Borders and Colors

With the spinner container set up, it's time to transform those inner divs into the vibrant, circular sectors we need for our animation.

This is where things get a bit more interesting:



.spinner-sector {
  position: absolute;
  width: 100%;
  height: 100%; 
  border-radius: 50%;
  border: 15px solid transparent;
  mix-blend-mode: overlay;
}

.spinner-sector-red {
  border-top-color: lightcoral;
}

.spinner-sector-blue {
  border-top-color: lightblue; 
}

.spinner-sector-green {
  border-top-color: lightgreen;
}


Enter fullscreen mode Exit fullscreen mode

Let's break this down bit-by-bit:

  1. position: absolute - This allows us to absolutely position the sectors inside the relatively positioned .spinner parent. This is the key to getting that overlapping circular layout.

  2. width/height 100% - Making the sectors fill the entire spinner container area.

  3. border-radius: 50% - Applying a 50% border radius to create a circular shape.

  4. border: 15px solid transparent - Using a fat transparent border as a sneaky trick to create just the outer circle perimeter we want. The transparent middle is where we'll rotate through.

  5. mix-blend-mode: overlay - This PDNSensitive property allows the sector colors to overlay and blend when they overlap during animation. It's how we get those awesome combined hues!

Finally, we set the border-top-color for each sector to the respective red/blue/green shades.

Since the transparent border doesn't render any color in the middle, the top border creates those thin colored rings that we want for the spinner segments.

This is how our code looks so far:

code outcome

Not fancy yet, I know, but we're making progress!

We've now styled those sector divs from sad boxes into circular, colored rings. They're shaping up quite nicely (pun very much intended).

But they're just...sitting there. Frozen in time. We need to harness the true powers of CSS and make these sectors spin!

Luckily, that's where CSS animations really shine. It's...keyframe time!

Spinning the Sectors

The core of any CSS animation is the @keyframes rule.

This allows us to define specific points along an animation sequence that transform the styled element.

For our loading spinner, we want a continuous rotating animation for those colored sector rings.

Here's how we'll animate that circular spin using keyframes:



@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg); 
  }
}

.spinner-sector {
  animation: rotate 1.5s ease-in-out infinite;
}


Enter fullscreen mode Exit fullscreen mode

That @keyframes rule at the top defines the start and end points of the rotation animation we want each sector to follow.

At 0% (from), we start with no rotation transform. Then at 100% (to), we rotate the full 360 degrees to complete one full spin.

With those keyframes set, we can hook up the animation on the .spinner-sector elements themselves using the animation property shorthand.

The rotate value corresponds to our @keyframes name, 1.5s sets the duration to 1.5 seconds, and ease-in-out makes it ease gently into and out of the spin.

The infinite value is what makes this animation run on a continuous loop - no stopping after one spin!

As soon as it hits that 360deg rotation, it resets and keeps on spinning indefinitely.

the loading icon half done

So, if you coded this up with me so far, you'd see those sector rings animating in a perfect, smooth, endless rotation cycle much like the one above.

But...something's still missing.

If you remember the original loading icon, those sectors aren't all spinning at the same pace.

We need to mix up the speeds and stagger the animations so they create that hypnotic overlapping effect.

Staggering the Spin: Randomizing Animations

Having all three sector rings spinning at the same constant speed is cool and all, but it lacks that extra little something to really grab your attention.

To replicate the hypnotic loading icon we're aiming for, we need to throw in some randomization to how those sectors animate.

Luckily, that's as easy as tweaking a few animation properties for each sector. Check it:



.spinner-sector-red {
  animation: rotate 2.2s ease-in infinite;
}

.spinner-sector-blue {
  animation: rotate 1.6s ease-out infinite; 
}

.spinner-sector-green {
  animation: rotate 2s linear infinite;
}


Enter fullscreen mode Exit fullscreen mode

Instead of applying the same 1.5s ease-in-out animation to every sector, we mix it up with different duration and timing function values.

The red sector has a longer 2.2 second duration with an ease-in timing, meaning it has a slower start before speeding up.

The blue sector clocks in at a snappier 1.6 second pace with an ease-out effect for a faster start.

And the green sector keeps it simple with a 2 second linear animation for constant, unwavering spin speed.

With these randomized animations in place, the sectors are now out of sync - constantly overlapping and unlocking from each other in visually appealing ways as they spin.

But we're still missing one crucial element - the pulsating text indicator in the center to really drive home that "loading..." message.

Not to worry, I've got one more trick up my sleeve for that!

Pulsating the Text

What's a loading icon without some text to let you know...well...that it's loading?

Our spinner is looking great with those staggered, spinning sectors.

But it's missing that pulsating "Loading..." text in the center to really tie the whole animation together.

Adding that fade-in-out text effect is just a few more CSS keyframe rules away:



@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}

.spinner-text {
  font-size: 2rem;
  animation: pulse 1.5s ease-in-out infinite;
}


Enter fullscreen mode Exit fullscreen mode

This @keyframes rule called "pulse" defines how we want the text's opacity to change over the course of the animation loop.

At 0% and 100%, we keep it fully visible with opacity: 1. But at the 50% midpoint, we fade it down to an opacity of 0.25 to create that subtle pulsating effect.

With those opacity keyframes set, we apply the "pulse" animation to our .spinner-text element, setting the duration to 1.5 seconds with an ease-in-out timing function.

We also bump up that font-size to 2rem so the text has a little more presence.

And just like that, we've got ourselves a beautifully pulsating "Loading..." message fading in and out in the center of our spinning icon!

The final piece of the puzzle to recreate that classic loading animation.

the final product

Between the staggered spin speeds, the color blending overlaps, and the pulsating text, this thing is a transfixing masterpiece of CSS animation.

Honestly, I could get lost watching it for hours...which may or may not be a good thing when you're waiting for content to load.

Either way, I hope this step-by-step coding journey has helped demystify how you can create these types of mesmerizing animations using just HTML and CSS!

The key is thinking through each piece - container, shapes, animations - and building up the parts using core CSS principles.

With a few creative tricks and some keyframe magic, you can craft all sorts of living, breathing interfaces.

That's all for now, thank you for following to this point. Wishing you a happy coding career.

. . . . . . . . .