Accessible lazy-loading with a <noscript> fallback
Designing for the probably even less than 0.2% may sound like an overkill. Static sites (such as Gatsby, Svelte, Jekyll, Hugo, 11ty etc.) will cover non-script users most of the time. But if you need more fine-grained control, <noscript>
is here to save the day.
If you turn off JavaScript in a browser, and open up any React app, you’re quite likely to see the sentence “This app works best with JavaScript enabled” or similar. Looking at the source code[1], this is the content of a <noscript>
tag — content that is only rendered when JavaScript is not available.
You can put mostly anything inside <noscript>
, including <meta>
, <link>
and <style>
tags. It’s a good place for fallback content and stylesheets.
A real-life use case: lazy-loading images •
Using a library like vanilla-lazyload already improves accessibility of a website, because it makes load time much faster, hence a smoother experience for users on average networks or devices.[2]
The core concept behind lazy-loading is either omitting the src
and replacing it with a data attribute, or using a tiny version of the image with a blur-up effect.[3] When the actual asset is loaded, it’s faded in using CSS. I’m omitting image width and height here, but you should try to set it to prevent content reflow.
HTML
<img data-src="cat.png" alt="My cat" class="lazy" id="cat" />
JS
const catImg = document.getElementById(‘cat’)
const fullsizeSrc = catImg.dataset.src // from the data-src attribute
const loader = new Image()
loader.onload = () => {
catImg.src = fullsizeSrc
catImg.classList.add(‘loaded’)
}
// VERY important: .onload() has to come before .src assignment
loader.src = fullsizeSrc
CSS
.lazy {
opacity: 0;
transition: opacity .5s ease;
}
.lazy.loaded {
opacity: 1;
}
The (admittedly edge-case) problem with these is, if there’s no JavaScript, the images will never be loaded. The solution is to add a non-lazy version for every lazy image, like so (and you can do this programmatically, at build time):
<img data-src="cat.png" alt="My cat" class="lazy" id="cat" />
+ <noscript>
+ <img src="cat.png" alt="My cat" />
+ </noscript>
It will still be super fast, because whatever is inside <noscript>
won’t load unless JavaScript is off. There’s still one issue to fix, though: hiding the lazy image placeholders when the <noscript>
version kicks in. Here’s how, anywhere in your <head>
:
+ <noscript>
+ <style type="text/css">
+ .lazy { display: none; }
+ </style>
+ </noscript>
Alternatively, you can load an external stylesheet too.
That’s it for inclusive lazy-loading — let me know if I’ve missed something!
See react-redux-ts/index.html at master · c0derabbit/react-redux-ts · GitHub ↩︎
Let’s not forget that as developers and/or designers, both our devices and network are likely to be superior to that of the average user. ↩︎
I’ve written about a React solution for the blur-up lazy loading here. ↩︎