I recently launched a CSS animation course for designers and developers wanting to improve their web animation skills. While building the course, I ran into the issue where content would animate before assets had downloaded. This article covers the approach I put together to fix the problem and ensure all animations played when expected.

We’ve all been there. For example, we want to fade in a hero header on load, so we add the fade-in keyframes, setting up the animation, but then the animations starts before the background image is downloaded. We get a half-loaded image fading in, and even worse, our logo or headline appear before the background is ready.

Thankfully there’s a way we can fix it.

The Issue: Parallel Events

When we load a website, the browser tries to make things are fast as possible by downloading and rendering the HTML and CSS while downloading other assets such as images in parallel.

This loading style can mean we get to see some layout and text content more quickly, but if you’ve created a large, magazine style header, the image might not arrive in time.

Let’s use some built-in browser tricks to put the brakes on the animation until the right moment.

load events and animation-play-state

Browsers give us a handy JavaScript load event when content has finished loading. That event will fire for elements such as images and scripts. We can use this to control when our animations play.

We’re going to make use of some JavaScript to listen for a load event, and make use animation-play-state to pause our animations until the event.

The following JavaScript should do the trick.

document.body.classList.add('js-loading'); window.addEventListener("load", showPage); function showPage() { document.body.classList.remove('js-loading'); }

Here’s what the code does. The first line adds a js-loading class to the body element. Then it sets up an event listener.

The event listener waits until the load event occurs, and then run the function removeLoadingClass . At this point, all the images and other assets have downloaded.

Lastly the removeLoadingClass removes the class from the body tag.

This code should be added to the HTML of your page, such as the head . If loaded in from an external file, the CSS could load and be parsed before this code executes, which would give the animations a chance to start before we’re ready.

Let’s use this class to make any on-page animations wait until the content is ready.

Waiting for one image

This approach waits for all assets on a page to load. You might want to only wait for one image, in your header for example. Thankfully there are load events for each image. The approach set out in Measuring Image Widths in JavaScript can help.

We can adjust the JavaScript to focus on one specific image.

// Adjust the "querySelector" value to target your image var img = document.querySelector("img"); document.body.classList.add('js-loading'); img.addEventListener("load", removeLoadingClass); function removeLoadingClass() { document.body.classList.remove('js-loading'); }

The animation-play-state Property

The animation-play-state property is well supported by modern browsers. It tells the browser whether the current animation is running or paused. By default animations are “running”.

We can use this property to make any animations on the page “pause” while we’re loading the content. We add this to our CSS.

.js-loading *, .js-loading *:before, .js-loading *:after { animation-play-state: paused !important; }

This code sets the play state of everything on the page to “paused”, whenever the body has the class js-loading .

It will make sure it applies to all of the :before and :after pseudo-elements also.

When JavaScript removes the js-loading class from the body tag, the rule no longer applies and all animations will be in their expected running state.

A nice benefit of this approach is that we don’t have to change anything else in our stylesheet!

What if JavaScript fails?

This is always a question worth asking if we rely on JavaScript to handle displaying content on screen. Sometimes JavaScript fails. It can be disabled. Plugins can do unpredictable things. While in most cases this will work, JavaScript is always a little outside our control so it’s good to think about what would happen if the JavaScript didn’t work as expected.

In this case, I think we’re good. If the JavaScript doesn’t run, it won’t apply the js-loading class. The animations will play straight away. This might result in a little strangeness with the background image loading as it animates, but that’s a worst case scenario and a reasonable fallback.

See it in action

Let’s test this to see it working. We need a large image. I found this rather gorgeous Nasa photo on Unsplash. It’s over 2MB in size.

When the page loads we should see the blank screen initially. To really see it in action we can use Chrome’s built-in throttling feature. Opening the inspector, select the “Network” tab, then open the dropdown containing speed presets. From this, we select “Good 3G”, which should be slow enough to see this in action.

A preset Network speed in Chrome

Press “Rerun” on this demo and no animations should play until the image has fully loaded.

See the Pen Using load and animation-play-state to wait till image has loaded #2 by Donovan Hutchinson (@donovanh) on CodePen.

Try removing the JavaScript (and clear the cache before reloading) to see the difference!

Loading Spinner

If you want to avoid a blank page at this point, you ought to have some loading text or animation that lets people know. It depends on how long you think the delay might be. If you have an enormously large image and the page tends to wait for more than a second or two, then a spinner might be a good idea.

On the other hand, it might be worth compressing the image more or scaling it down so that it loads more quickly. Experiment and see what works best for you.

I hope this technique helps you keep your animations working together.

If you’d like to know more about animating your web sites, you might enjoy the tutorials over on CSSAnimation.rocks. Follow along on Twitter at @cssanimation.