This week I spent some time working on my website's loading performance. I started by switching from Slick to Glide.js in order to remove jQuery as a dependency altogether. This helped me reduce the amount of JavaScript and CSS used by half (!). I then added a language preference cookie. Finally, as a simple way to enhance the user experience, I added a function which would make the switch automatically depending on the browser's language settings.
Everything was running smoothly, but I couldn't help but notice that my site was suffering from a Flash Of Unstyled Content, AKA a "FOUC". It was really noticeable even with the new JavaScript and CSS in place: once a link was clicked, the page would start rendering almost immediately and then the CSS would get applied. This was really annoying as it removes the user from this smooth, almost instant experience I was aiming at. Fortunately, there are a few things we can do to prevent this from happening and get rid of that pesky FOUC.
Step 1: Hide everything!
The first thing we want to do is simply to add a CSS instruction so that our body is hidden from the page until it is ready to be unveiled. This allows the page to be fully loaded before we can finally present it to the user. This might be counter-intuitive since we're aiming at speed, and, well, we're slowing things there, but it's a sacrifice we're making for the sake of the user's experience.
<body style="visibility: hidden;">
<!-- Your awesome website -->
</body>
We could go with opacity
instead, and make use of CSS transitions to add a bit of magic.
Step 2: Unveil when everything is ready
We then need to revert that visibility
CSS property once the DOM has been loaded and is ready. For that, I'm using a simple helper function, a bit like jQuery's document.ready()
method. It calls a callback method once the document is in a "complete" or "interactive" state.
So we simply change the visibility
property of my <body>
tag to visible
.
// Helper function
let domReady = (cb) => {
document.readyState === 'interactive' || document.readyState === 'complete'
? cb()
: document.addEventListener('DOMContentLoaded', cb);
};
domReady(() => {
// Display body when DOM is loaded
document.body.style.visibility = 'visible';
});
And there you go! Our FOUC is gone. With this simple trick, our users get a better experience and don't have a weird mess blinking on their screen before being able to browse our site.
The Firefox hack
While things should run smoothly on Chrome, you might still see a flash on Firefox. I struggled to find a solution to this problem until I stumbled upon a bug in Firefox that's been reported 3 years ago and is still active. People are still trying to fix this but lucky for us there's a simple hack we can use as a workaround to this problem. Simply add a dummy piece of JavaScript code right after your <body>
tag and you should be all set!
<body style="visibility: hidden;">
<script>0</script>
</body>
Pretty neat, huh? Pretty weird also, I must confess. But hey, it does the job.
Note: Think of the noscript people
Don't forget that not everybody can or want to execute JavaScript. In that case, this simple line right before our closing </body>
tag will help make our site seen by everybody.
<noscript><style>body { visibility: visible; }</style></noscript>
And we're all set! Now our site should be displayed correctly, without any FOUC. Yay! 🎉
Update - May 1, 2020
It has been reported that my code breaks W3C's code validator. That's because officially, the <style>
tag cannot be a child of <noscript>
.
To fix this problem, what we can do is remove this <noscript>
tag, and add a no-js
class on the body
element. Then, we simply add this CSS rule in the <head>
of the document:
<head>
<style>
.no-js {
visibility: visible;
}
</style>
And finally revert this once again right after the <body>
tag thanks to JavaScript:
<body style="visibility: hidden;" class="no-js">
<script>
document.querySelector('body').classList.remove('no-js');
</script>
This will not only make this W3C compliant, but since we've added a bit of JavaScript in the body of our document, the dummy JS code we added earlier becomes obsolete! So now, everybody's happy, and we can finally grab a fresh glass of water and enjoy the sun.
Update 05/06/2021: Thanks to Moritz Profitlich for correcting a small typo in the source code of this article! 😄