Lazy load your images with a Service Worker

by Jon Robson
September 23rd, 2015

I wanted to show a novel use-case for Service Workers - a modern method for lazy loaded images spurred on by Christian Heilmann's nice trick with the HTML template element. Service Workers are the most exciting thing to come to the web since geolocation in my opinion. In a nutshell they are bits of JavaScript that run in the background and let you do neat things such as intercept network requests. Jake Archibald quite rightly points out you can do some "cool shit" with them now and I think this is pretty neat. I'm still not sure whether what I am doing counts as breaking the web... I can still curl the page and get the HTML complete with all the nice image urls. "If it doesn't load through curl, it's broken" doesn't seem to apply with a Service Worker... I think!

The problem

I'm pretty surprised about the amount of people I talk to who believe that images not seen on a page are never loaded. Browsers are clever these days, they tell me. If an image is below the fold on a mobile device, it's not downloaded. That's simply not true.

If a tree falls in a forest... Images are a big problem in the developing world. When we load too many images, we are not only making our sites slower, but we are inflicting large data charges for the people that use our sites. We have a responsibility. The Barack Obama article on English Wikipedia has over one hundred images on the page. These image requests compete for network requests for render-blocking resources that can impact the first paint. We need to do something!

A solution

I've implemented a super simple Service Worker that lazy loads images without rendering the page useless for non-js users.

This page installs a script that on first-load will register a ServiceWorker which intercepts all page network requests and using a regular expression substitutes all `src` attributes (you may also want to apply it to srcset too) with data- equivalents that can be loaded via JavaScript. Thanks to the beautiful Fetch API this was pretty easy to get setup.

With those images out of the way, we now need to show them to the user in a more clever fashion. We can rely on the user having JavaScript at this point if we detect a src data attribute - as it means the user has the Service Worker installed. The script will then only load images when you scroll to them. The code for this is rather simple thanks to the awesome getBoundingClientRect. Right now I'm keeping it super simple and only loading when they appear in the viewport. In the future, I think we can be more clever about this by only loading images when the image is (say) two viewports away or after render blocking resources have fully loaded. I've ironically been very lazy and used jQuery.

Limitations

Right now you'll need Chrome to make use of this part of the Service Workers API, but our friends in Mozilla are working on it and it's only a matter of time before it lands there too. As web developers we have a responsibility to adopt things to speed up and help prioritise their implementation in browsers. Remember what happened when geolocation and other friends became cool? You'll also need HTTPS - this is a requirement of a Service Worker.

Practical uses

I work for the Wikimedia Foundation. Right now I'm interested in how this can be used to improve the Wikipedia experience on 2G connections. Early investigations show 2G connections take fifty seconds to load Barack Obama! Just by lazy loading images using this Service Worker, we reckon we can get that down to twenty seconds. We don't plan on stopping there, though -- we have plans to get it down below the fifteen second mark.

Other solutions

An alternative to HTML parsing is to simply hijack image requests. To do this instead of rewriting your HTML with regular expressions you simply check whether the request is for a file in the image directory and intercept it. The downside of this is right now there is no way to tell the type of the request, so this means if you try and access the image resources outside the page and the ServiceWorker intercepts you'll have to hard refresh to overcome the ServiceWorker. I played around with this idea and was able to block image requests on a certain query string parameter.

<img src="image/foo.jpg?sw=true"
alt="any requests with the sw=true parameter would be returned by the Service Worker">
<img src="image/foo.jpg"
alt="A requests for an image without the sw=true parameter would return the transparent gif">

Demo

Your thoughts

Feedback welcomed on my Twitter post or any of the Phabricator projects linked above. We're an open source project, so come help us make this happen! :-)

I remain your humble and obedient servant.
Sincerely yours,
Jon Robson