Service Workers: Dynamic Responsive Images using WebP Images

Images play an important role on the web today. Imagine a world without images on our web pages! High quality images can really make a website stand out, but unfortunately they come with a price to pay. Due to their large file sizes, they are bulky to download and result in slow page load times. If I'm a user with a low bandwidth connection, it can be a pretty poor experience.

This is even more important for mobile devices. Loading large images on a mobile device can take time depending on your network and connection speed, and if you are an impatient user like myself, you'll quickly become frustrated. Fortunately, we have the power of responsive images at our disposal. Using the picture element, we can provide our users with a different image size, device pixel ratio (DPR) and format depending on their device. For example, the code below will allow you to do just that.

<picture>
    <source
        media="(min-width: 1024px)"
        srcset="./images/brooklyn.jpg, ./images/brooklyn-2x.jpg 2x, ./images/brooklyn-3x.jpg 3x"
        type="image/jpeg">
    <source
        media="(min-width: 320px)"
        srcset="./images/brooklyn-small.jpg, ./images/brooklyn-small-2x.jpg 2x, ./images/brooklyn-small-3x.jpg 3x"
        type="image/jpeg">
</picture>

In the code above, we are specifying different image sizes and their corresponding device pixel ratios for a given screen width. Using the picture element, the browser can decide the best content based on the capabilities of the device. The code above works perfectly and we can extend it even further to accommodate more scenarios.

You may be familiar with the image format WebP. Compared to PNGs, they are 26% smaller in file size and around 25-34% smaller in file size compared to JPEG images. That's a pretty decent saving! By default, they are supported in Chrome, Opera & Android, but unfortunately they aren't currently supported in Safari or IE. Given that we have the picture element at our disposal, there is no reason why we can't start using WebP images today and allow the browser to fallback if they aren't supported.

Service Workers - WebP

Let's take the code above and add in support for WebP images. At the same time we are going to ensure that we return the best visual quality for different resolutions using DPR.

<picture>
    <!-- JPEG Images -->
    <source
        media="(min-width: 1024px)"
        srcset="./images/brooklyn.jpg, ./images/brooklyn-2x.jpg 2x, ./images/brooklyn-3x.jpg 3x"
        type="image/jpeg">
    <source
        media="(min-width: 320px)"
        srcset="./images/brooklyn-small.jpg, ./images/brooklyn-small-2x.jpg 2x, ./images/brooklyn-small-3x.jpg 3x"
        type="image/jpeg">
    <!-- WebP Images -->
    <source
        media="(min-width: 1024px)"
        srcset="./images/brooklyn.webp, ./images/brooklyn-2x.webp 2x, ./images/brooklyn-3x.webp 3x"
        type="image/webp">
    <source
        media="(min-width: 320px)"
        srcset="./images/brooklyn-small.webp, ./images/brooklyn-small-2x.webp 2x, ./images/brooklyn-small-3x.webp 3x"
        type="image/webp">
        <!-- The fallback image -->
    <img
        src="./images/brooklyn.jpg" alt="Brooklyn Bridge - New York">
</picture>

In the code above, we have created a picture element that uses both JPEGs and WebP images. The browser will decide the best option based on the capabilities of the device. Using WebP images does mean that you will need to keep a copy of the image in both WebP and JPEG format on the server because it isn't supported across IE and Safari. The code above works perfectly for our immediate needs, but imagine this code for every single image on your website - it could get really bloated! As your site begins to grow, writing code like the example above for each and every image asset can become quite tedious. This is where the power of Service Workers come in.

Service Workers - Dynamic Responsive Images

If we fire up our developer tools and observe the HTTP headers being sent to the server, we should notice the Accept header notifies us that this browser supports WebP images. You should see something similar to the image above - take note of the Accept header underlined in red. In order for us to take advantage of this and start serving WebP images, we need to register a Service Worker. A great feature of Service Workers is that they have the ability to intercept network requests which gives you full control of the content you'd like to respond with. Using this feature we can listen to the HTTP headers and decide what we want to do with them. If you'd like to learn more about Service Workers, I recommend checking out this Github repo for more information.

Let's start by adding the following code to our HTML page in order to register the Service Worker. The code below is referencing a very originally named file called 'service-worker.js'.

In the code above, we are doing a simple check to see if the browser supports Service Workers, and then registering and installing it if it does. The best thing about this code is that if Service Workers aren't supported by the browser, they will simply fall back and your users won't notice the difference.

Next, we need to create the Service Worker file and call it 'service-worker.js'. It will be used to intercept the requests that are being passed to the server.

There is a lot going on in the code example above! Let's break it down step by step.

In the first few lines, I am adding an event listener to listen out for any fetch events that take place. For each request that takes place, I then check to see if the current request is for a JPEG or PNG image. If I know that the current request is for an image, I can then determine the best content to return based on the HTTP Headers that are passed through. In this case, I am inspecting each header and looking for the "image/webp" mime type. Once I know the header values, I can determine if the browser supports WebP images and return the corresponding WebP image.

This now means that our HTML looks a lot neater and supports WebP images without the bloat!.

<picture>
    <source
        media="(min-width: 1024px)"
        srcset="./images/brooklyn.jpg, ./images/brooklyn-2x.jpg 2x, ./images/brooklyn-3x.jpg 3x"
        type="image/jpeg">
    <source
        media="(min-width: 320px)"
        srcset="./images/brooklyn-small.jpg, ./images/brooklyn-small-2x.jpg 2x, ./images/brooklyn-small-3x.jpg 3x"
        type="image/jpeg">
</picture>

Service Workers open up a world of endless possibilities and this example could be extended to include other image formats and even caching. You could easily add support for Internet Explorer's JPEGXR. There is no reason why we can't present our users with fast web pages right now!

If you would like to see demo of this code in action, please head over to deanhume.github.io/Service-Workers-WebP. Fire up your developer tools in a browser that supports these features, and you'll be able to see it in action!








Comments

Jake Archibald - 11/4/2015
This is great! You could use clients.claim() to bring this service worker into play earlier, meaning the page doesn't need to refresh to start using it. Also, it doesn't look like you read the body of the original request, so you don't need to clone it.

Dean Hume - 11/4/2015
Cheers Jake! Good advice - I'll update the code sample accordingly :-)

Justin - 11/5/2015
excuse my ignorance... one of the great things about the rwd image solution is that it works even with the browsers pre-rendering of images. Will this stop any initial download of the first matched src or srcset before the service worker kicks in?

Jake Wilson - 11/6/2015
Really cool, however I think it would be better to detect Webp support when the page loads (via Modernizr or something else) and then intercept the JPG/PNG requests using the service worker. The problem with your example is that is checks for WebP support on every single image request. I don't have experience using service workers so I don't have any idea what kind of performance hit that incurs. But its typically better to save that "does it support webp" value somewhere and simply reference it. Support for Webp is obviously not going to change from request to request. Pretty cool example of ServiceWorkers though!

Andrew Betts - 11/7/2015
Very cool idea, love it. Definitely helps keep markup more sane and maintainable. However, aren't we generally regarding the Accept header as a bit of a lost cause these days? Would your technique end up in the world of user agent sniffing at some point?

Dean Hume - 11/9/2015
Hey Andrew! Interesting news and thanks for the advice! I didn't know that the accept header was becoming defunct. Is this something that has changed recently?

zcorpan - 11/12/2015
The example that has both JPEG and WebP in picture is backwards. The browser doesn't pick the most appropriate `source`, it picks the first one that "matches", from top down. So if both JPEG and WebP are supported, your markup means JPEG will always be selected.

zetoun - 11/12/2015
a simple solution to replace jpg with webp add these lines in .htaccess file : http://pastebin.com/zXmmKbCf

Jonathan - 11/12/2015
"The example that has both JPEG and WebP in picture is backwards." Correct! See here (also the comments): http://blog.cloudfour.com/dont-use-picture-most-of-the-time/

Derek - 11/12/2015
That's all well and good apart from the fact that Safari doesn't support the picture element. http://caniuse.com/#feat=picture

Ramon - 12/14/2015
Will this service worker work on a css background url?


Add your comment

300 Characters left


Please fill this in to confirm that you are human