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.
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.
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!