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');
}
animation-play-state
Property
The 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.

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.
Thanks for the clear explanation and solution to this problem. Especially the idea that if JavaScript fails for whatever reason, the animations will still execute, if not optimally.
This is very useful, thank you.
I’m having a small issue with the Javascript, if I place that code in the head, the console will show an error “Uncaught TypeError: Cannot read property ‘className’ of null” – presumably because the body is not loaded right away, so it doesn’t add the class.
If I place the code inside of the body, it does work. Any solution to this?
Thanks
Wrap it in a document ready and it will work in the header too.
document.addEventListener("DOMContentLoaded", function(event) {
//document.body.....
});
The problem is the page is read by the browser from the top. If you add the code in a script tag under your content that would work, or you can keep the code in the header but wrap it in a DOMContentLoaded event callback (https://developer.mozilla.org/en/docs/Web/Events/DOMContentLoaded) or $( document ).ready() if you’re using jquery.
Hi Stephen,
Good question! Since this JavaScript needs to load as soon as possible we could put it in the , but then as you point out, the is not loaded at this point. We can adjust the JavaScript to instead put the class on the part of the page:
Here the “document.documentElement” part means the tag in this case. It should now work as before. Hope this helps!
Donovan
Thank you guys!
Donovan, that solution works perfectly.
Love the idea of this, but using either example I’m getting the error
Uncaught TypeError: Cannot read property 'className' of null
on the line withdocument.body.className += " js-loading";
…I’m sure this is pretty basic JS territory but I’m pretty basic. Any idea as to why this is occurring? Thank you!
Cool topic.
But the way adding and removing classes is awful.
Consider using
document.body.classList.add('js-loading')
anddocument.body.classList.remove('js-loading')
!Keep on hacking #DevTips
Good idea – thanks!
Nice article. I just wanted to point out that you don’t have to use
false
as third argument inaddEventListener
call, because it’s the default value foruseCapture
parameter.Also, I’d suggest to use the ClassList API to add/remove classes of your elements. It’s very handy.
Any thoughts about directly manipulating
animation-play-state
in javascript?I solved this problem for getting images to load before starting an animation without using Javascript, but the solution comes with its own drawbacks.
I base-64 encoded the images and inlined them in the HTML. This ensures that the images are loaded because they are part of the HTML, and CSS won’t start until the HTML is downloaded.
However, you bloat your HTML, and base-64 encoded images might be bigger than they would be otherwise.
In my case, it resulted in a negligible effect in the page load time, but that may not be the same for everyone!
This is super helpful, thanks!