A detailed tutorial to create wavy preloader animation with CSS using CSS3 Keyframes property.

I’ve been watching people doing lots of cool preloader stuff with CSS nowadays. I decided to give it a go and came up with a wavy animation loader (kind-of) effect.

It’s one those effects that I used to learn in my early days in web world with JavaScript. But who knows I’ll be creating this in CSS without relying on JS at all.

Let me show you a quick preview of the preloader animation:

CSS Wavy Preloader

Or check out the live demonstration for more fun.

Wavy animation and the preloader

The concept is quite simple. I have 5 dots in a series that move up-and-down in a wavy sequence with a nice bit of easing.

Creating the preloader layout

The preloader (.wavy-loader) is composed of 5 dots. I tried to keep it as simple as possible, I took 5 separate spans to create the 5 dots. See the below markup:


<div class="wavy-loader">

I made this spans look like dots with the help of below CSS:


.wavy-loader span {
  position: relative;
  border-radius: 50%;
  width: 8px;
  height: 8px;
  margin: 2px;
  background-color: white;
  opacity: .5;

Each dots is of 8x8px size, with a bit of margin in between. I’ve made the dots of white color here, as I want to work them over a dark background. You may tweak the colors as you wish. Plus I slightly adjusted the opacity, that I’ll explain in the next segment.

Animating the preloader

I have 5 dots in my preloader. In order to achieve a wavy animation effect, I have to animate each dot individually up-and-down with a little delay in between.

Again, CSS keyframes make all this possible for us with least complexity.


Don’t worry much about the length of the code. It looks long as it consists same but separate keyframes declarations for -moz and -webkit.

span:first-child {
  animation: wavy-loader 2.5s .1s ease-out infinite;

span:nth-child(2) {
  animation: wavy-loader 2.5s .2s ease-out infinite;

span:nth-child(3) {
  animation: wavy-loader 2.5s .3s ease-out infinite;

span:nth-child(4) {
  animation: wavy-loader 2.5s .4s ease-out infinite;

span:nth-child(5) {
  animation: wavy-loader 2.5s .5s ease-out infinite;

@keyframes wavy-loader {
  0%    { top: 2px; }
  1%    { top: 4px; }
  2%    { top:6px; }
  3%    { top:8px; }
  4%    { top: 10px; }
  5%    { top:12px; opacity: 1; }
  6%    { top: -2px; opacity: 1; }
  7%    { top: -4px; opacity: 1; }
  8%    { top: -6px; opacity: 1; }
  9%    { top: -8px; opacity: 1; }
  10%   { top: -10px; opacity: 1; }
  11%   { top: -12px; opacity: 1; }
  12%   { top: 0; opacity: .5; }
  100%  { top: 0; opacity: .5; }

Assuming you already know the implementation of CSS keyframes, here’s the explanation of the above CSS.

My wave has an amplitude of 12px – meaning the total height of my wave is 24 pixels. Lets focus on only 1 dot for now:

In the first 6 frames (0% to 5%)

I moved the dot 2 pixels down progressively per frame (the dot, with relative position, will move down with a positive top value).

In the next 6 frames (6% to 11%)

The dot jumps 2 pixels up of its initial position per frame (the dot will now move up with a negative top value).

12+ frames onwards

Our dot comes back to the original position (top: 0) with the default opacity value (0.5). This will make our wave wait for sometime before the animation repeats.

The Wave

I’ve added the animation to each dot (<span>) separately to allot a different, progressive delay in between them – this plays an important role in creating the wave animation. My animation executes for 2.5secs with ease-in-out easing.

Opacity bits

I had already set the opacity of the dots to 0.5, and now when a dot reaches 12px down and till it moves 12px up, I’m setting the opacity to 100% (opacity: 1). That will give a shining look to the dots as they move up in the wave.

See the final demo

That’s all folks! This may not be the perfect way of creating this effect, but this was my take on that.

If you wanna see a more smoother animation like this, I’ve found this beauty later that made me smile and think more in this direction. Let me know what you think of the one I created. Enjoy :)