Updating CNN's Loading Animation

The other day I opened CNN.com and was immediately greeted with the following loading animation.


That's the actual animation, not a low-res screen grab. Two visuals immediately caught my eye:

  1. Jagged, pixelated edges
  2. Abysmal, low frame rate

I opened Chrome's Dev Tools to inspect the element. Turns out the loading icon is nothing more than a low-quality animated GIF, coming in at ~2kb. As a CSS-fan I know there are more-performant, better-looking solutions to this GIF. So I set out to build an alternative.

Turns Out, It's Really Simple

Here's what we'll be building:

We'll start with the HTML structure - a simple nested <div>:

<div class="cnn-loader-stage" role="progressbar" aria-busy="true">
  <div class="cnn-loader"></div>

Now for the CSS. First, the outer .cnn-loader-stage element:

.cnn-loader-stage {
  position: relative;

  perspective: 100px;
  transform-style: preserve-3d;

The important styles here are the 3D properties - perspective to produce the 3D depth effect as the animation rotates, and transform-style: preserve-3d to indicate the children of the element should be positioned in the 3D-space.

By itself the .cnn-loader-stage produces nothing to see. So let's take a look at the inner .cnn-loader element:

.cnn-loader {
  width: 50px;
  height: 50px;
  background-color: #cc0000;
  border: 1px solid #aa0000;

  position: relative;

  backface-visibility: visible;

  animation-name: rotateFlip;
  animation-duration: 1.4s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;

The width and height of 50px are merely to match the size of the animated GIF, and can be changed.

The background-color and border are the entirety of the styling - a CNN-red with a slightly darker 1px border.

Then comes the important stuff...

First, backface-visibility so the back of the element is visible when the element is turned around. This isn't really necessary (and could be removed here) but I like to be explicit.

Then the animation properties:

  • animation-name: rotateFlip - to define the animation keyframes
  • animation-duration: 1.4s - to match the existing animated GIF
  • animation-iteration-count: infinite - to make the animation repeat over-and-over-and-over
  • animation-timing-function: linear - to match the animated GIFs transitions

And finally we have the rotateFlip animation keyframes:

@keyframes rotateFlip {
  0% {
    transform: rotateY(0deg) rotateX(0deg);

  55% {
    transform: rotateY(180deg) rotateX(0deg);

  100% {
    transform: rotateY(180deg) rotateX(180deg);

This produces two rotations, one after the other. First, the square is rotated along the Y axis, followed by the square being rotated along the X axis - this produces the effect of flipping the square from left-to-right, then flipping the square top-to-bottom. This animation then repeat infinitely.

Cross-Browser Compatibility

For the sake of brevity I've removed browser prefixes from the above code samples. The demo below adds all the necessary browser prefixes to make the loader work in all modern browsers.

File Size

Even with browser prefixes the entire CSS and HTML for the loader comes in at under 1.2kb, or barely over half the size of the animated GIF. As an added bonus our CSS solution doesn't require an additional HTTP request, unlike the animated GIF.


Using Chrome Dev Tool's Performance Monitor I recorded 10 seconds of activity and rarely did the animation fall below the target of 60fps.


The Frames Per Second Meter showed a similar rate.


The 60fps mean the animation appears smooth, providing a great user experience while waiting for content to load.


Here's the final CSS solution (left) with the existing animated GIF (right).

See the Pen CNN Loading Animation (compared to existing animated GIF) by Brett DeWoody (@brettdewoody) on CodePen.