Monday

CSS Only Loader Animation

As I test out code changes in my local environment, I always run through this interstitial/waiting page that contains a loading animation. This animation is generally done via an animated .gif, or a .swf movie. Today while waiting for my app to load some backend stuff, it occurred to me that I could pretty easily replicate this same thing in pure HTML + CSS, thus making changes to color or shape super simple.

Here's a screenshot of what I'll be replicating:

NOTE: I'm doing this assuming Chrome browser, and so will be leaning heavily on CSS3, and the -webkit- prefix. At the end of this post, there's a link to site that does a bunch of awesome automated stuff for all this, so I'm eliminating all the complexity for the sake of this talk.
Update: Just added some -moz- support too, though Firefox doesn't animate as nicely as Chrome.

HTML

First, the markup. I figure I'm looking at a div that contains 5 objects. I'm going to call this thing "loader", and each object will just be a span (because they're inline) with a class of "frame" (though I probably don't need a class on these, I wanted to add them for clarity).

So, our HTML markup will look like this:

CSS

Basic Stuff

Let's now add in some basic styling for this thing. I really don't need to do anything with my #loader container, though I certainly could add some margin offsets or whatever I want. But my frames do need some love. I'm starting by defining a light grey background:

.frame {
    background: #999;
}
I'm going to set these to display: inline-block, because I want them still inline like spans want to be naturally, but also want them to have the dimension properties of block. I also want to set the size of these to 20px square, and add a right margin to offset them from one another:
.frame {
    background: #999;
    display: inline-block;
    width: 20px;
    height: 20px;
    margin-right: 10px;
}
That should get us looking like so:
Now let's make those squares into circles by simply defining a border-radius that's half as big as our width and height dimensions, and because I want to animate opacity too, I'm going to define something nice and light as a starting point:
.frame {
    background: #999;
    display: inline-block;
    width: 20px;
    height: 20px;  
    margin-right: 10px;    
    border-radius: 10px;
    opacity: .3;
}

Animation

Alright, now comes the really fun stuff. First, some additional reading as a point of reference: W3Schools.com: CSS3 Animations.

I need to define a name for my animation (sort of like a javascript function; I'll call ours "loader"), a duration for my animation (in this case I want my animation to last one second), and how many times I want this thing to repeat (in our case, I want it to repeat forever):

.frame {
    background: #999;
    display: inline-block;
    width: 20px;
    height: 20px;  
    margin-right: 10px;    
    opacity: .3;
    border-radius: 10px;
    -webkit-animation-name: loader;
    -webkit-animation-duration: 1s;
    -webkit-animation-iteration-count: infinite;
}
Nothing is going to happen yet though because we need to define what, specifically our animation is supposed to DO. Essentially, I want these circles to:
  1. Fade in/out
  2. Change color
  3. Grow slightly
So what I really want to define is a starting point (the base style for each where each .frame starts from), a middle point (how I want each of these to look my change), and and end point that gets us back to the start again. Each of these points are referred to as "keyframes", and we must define WHERE each of these keyframes take place in our animation. I like to think of animations like cells of a movie reel, or pages in a flip book, only instead of having to define every tiny movement between each .frame, the browser (via CSS3 animation) handles that for us. We can define our keyframe positions with a percentage:
@-webkit-keyframes loader {   
    0%   {}
    50%  {}
    100% {}
}
Now that we have our keyframes for our animation defined, we can define WHAT we want to happen at 0%, 50%, and 100% of our animation. Recalling my list of things I want to do (fade in/out, color change, size change), I'll add those in my keyframes. Note I don't need to define every property of my starting and ending points, because that's handled with our .frame styles we defined earlier. As such, I just need to define how these look in the middle of the animation, in this case opacity of 1, background-color of red, and I want to scale them up just a touch:
@-webkit-keyframes loader {   
    0%   {  }
    50%  {  opacity: 1.0; 
            background-color:red; 
            -webkit-transform: scale(1.2); }
    100% {  }
}
Now We have a nice set of circles that all sort of pulse together from slightly transparent light grey to a full opaque and slightly larger red. But we want each of these to sort of follow each other in the animation; that is, we don't want all of them pulsing simultaneously, but rather independently, and one right after the other. I can do this using nth-child selection and a keyframe animation delay for each of my 5 .frame objects. Because my animation is 1 second long, and I have 5 objects, I'll just divide 1/5 and set each animation delay to offset by .2 seconds; that CSS will look like this:
.frame:nth-child(1) {
    -webkit-animation-delay:0.2s;
}

.frame:nth-child(2) {
    -webkit-animation-delay:0.4s;
}

.frame:nth-child(3) {
    -webkit-animation-delay:0.6s;
}

.frame:nth-child(4) {
    -webkit-animation-delay:0.8s;
}

.frame:nth-child(5) {
    -webkit-animation-delay:1s;
}
Sweeeeeet. Now we're cooking with fire! So, what I've done is just delayed when the animation is supposed to start for each of my .frame objects, and presto - we've replicated our loader.

Changing color or shape

Before, my loading page used either an animated .gif/.swf, which if I wanted to change the color of these circles means I'd have to have extra software, or at the very least spend a little time re-working my graphics so I can output the new file. But now, if we wanted to change this to say, blue and square, all we need to do is edit some CSS:

.frame {
    border-radius: 0;
}

@-webkit-keyframes loader {   
    50%  {  opacity: 1.0; 
            background-color:blue; 
            -webkit-transform: scale(1.2); }
}

Demo


The above demo won't animate unless you're using a Mozilla or Webkit browser

Demo with Full Code (Demo currently works only in Webkit or Mozilla browsers)

Automated Way!

Of course, after having gone through this whole exercise, it's important to note that the internet saves the day by having online generator tools that do this for us; like this, totally awesome site cssload.net. Even still, it's fun to figure this stuff out so I better understand how it works.

2 comments:

  1. Yes, but we're not there yet: http://caniuse.com/#feat=css-animation. The W3C Working Group hasn't finalized these specifications yet, which means we are forced to either use prefixed implementations, or not use these features at all. Up to you how you want to move forward.

    Thanks for reading!

    ReplyDelete