Ensuring CSS Animations Run and Stop Gracefully

CSS animations are used as pre-loaders all over the place. But, as you’ll see, there are drawbacks.

The standard method of pre-loading content is to show an animation, load some content, then simply stop the animation after the content loads. Simple enough, but nailing this timing for when both of those do and should happen isn't so simple.

Since we don't know how long it takes to load that content, many developers will simply keep the animation running in the background (possibly for infinite), or they will stop the animation "half way" right when the content finishes loading, but before it completes its iteration.

Both options stink. The first is bad for performance since hidden animations still drain CPU (especially on mobile) while the second option leaves the user left with a jarring experience; stopping the animation and fading in content the same time the pre-loader's fading out. We can fix this.

Some Goals

  1. Only play animations as long as needed
  2. Allow an animation to complete its iteration when told to stop
  3. Listen for when the animation iteration completes, so we can apply other styles or functions

Our Solution

Ensure Animation is a simple JS library that listens for animation events on a given node and checks to see if the animation should continue running or not. By default, the animation will keep running until a given class name is applied (or is stopped manually) at which point Ensure Animation will ensure our animation completes and ends after its current iteration.

This solves two concerns above, preventing the jarring stops "half way" through an animation and running needless animations. Our last concern is resolved by just adding a class to signal the animation has completed its iteration. Check Out Some Demos Here!

Cool demos, now show me the code

Example 1: Fading in a Lazy Loaded Image

Using the popular Lazy Sizes package, we can show a pre-loading animation while our huge image loads, then gracefully end the animation and fade in our image.

<img data-src="very-large-image.jpg" class="hero lazyload">
<div class="preloader" data-ensure-target=".hero" data-ensure-until=".lazyloaded" data-ensure-finish-class="fade-in"></div>
import EnsureAnimation from 'ensure-animation'
import 'lazysizes'

new EnsureAnimation('.preloader')

This code just works.

It will continue playing animations until the lazyloaded class name (Specified in data-ensure-until) is applied to our target .hero node (Specified in data-ensure-target). This works without any customization because Lazy Sizes automatically looks for any images with a lazyload class and applies a lazyloaded class name to the image when it loads. Then, using Ensure Animation, we add a fade-in class (Specified in data-ensure-finish-class) that simply turns the opacity to 1, allowing the image to fade in when our animation completes its last iteration.

Note that both data-ensure-until and data-ensure-target work like document.querySelector. So, if you wanted to continue running an animation until our target had a specific data attribute, you could do something like: data-ensure-until="[data-loaded='true']".

Example 2: Displaying Ajax Loaded Content Gracefully

Many times we’ll show a pre-loader while making an ajax request, but when the content is done loading we'll just remove the pre-loader and throw the content onto the page. That's no fun. Using Ensure Animation, we could:

const preloader = new EnsureAnimation('.preloader')[0] // get our first instance
const button    = document.querySelector('.button')
const content   = document.querySelector('.content')

button.addEventListener('click', function(){
  var xhttp = new XMLHttpRequest()
  xhttp.onreadystatechange = function() {
    preloader.finish().then(() => {
      content.innerHTML = this.responseText
      content.classList.add('fade-in')
    })
  }
  xhttp.open('GET', 'https://static.viget.com/content.txt', true)
  xhttp.send()
}, false)

Notice we’re adding a fade-in class on line 11 to the node that receives the ajax loaded content (reponseText) only after our animation has ended and our content has loaded. We can do this because it returns a promise that resolves when the animation completes it iteration. Cool, huh?

How Can I Get It

Ensure Animation is available via NPM, so installing it on your project is as easy as:

npm install ensure-animation

Hope you found this library useful! Feel free to post other examples you can think of in the comments!

Tommy is a front-end developer who builds dynamic, interactive sites for Viget clients such as ESPN, Dick's Sporting Goods, and PUMA. He works in our Falls Church, VA, HQ.

More articles by Tommy