Peter Keating

Developer from the New Forest in the South of England.

Reducing Page Size with WebP


Images often take up the majority share of content downloaded during page load on a website. Generally websites are heavier in weight nowadays, so any savings that can be made are welcome in order to speed up page loads for the user. When attending the amazing responsive day out in Brighton, Bruce Lawson gave a talk on what is coming to standards in the future. In his talk, Bruce touches on the use webP as a modern alternative to JPEG & PNG for reducing the size of images with no loss in quality. Google introduced webP as a modern alternative a couple of years ago to provide an image format with better compression resulting in reduced file sizes. Below is their explanation of the webP image format.

WebP is a new image format that provides lossless and lossy compression for images on the web. WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller in size compared to JPEG images at equivalent SSIM index.

I won't go into detail about how it works, as the official webP page explains that better than I ever could. This blog post is going to focus on the implementation of webP images to reduce the load file of images by 70% for this blog. Although the number of asset images on this blog are minimal (my ugly face in the header, post related icons & social media icons), I think the implementation was an interesting process worthy of sharing.

Converting to WebP

The first step in the process is to convert all the asset images to webP format. There is a simple command line tool called cwebp encoder, which is available on the downloads page of the official webP site in the archive file beginning with libwebp. There are versions available for Windows, Mac and Linux, personally I am on a Windows machine so I downloaded the libwebp-0.2.1-windows-x86.zip file. The archive file contains instructions, the cwebp encoder for converting images to webP format, and the dwebp decoder for converting webP images to ordinary images.

Once you have the cwebp converter there is a command that requires the file name of the image you want to convert, and the file name of the webp image that will be outputted. Other options are available if you wish to tweak the parameters that are used during conversion to vary the quality of the output. The example below will convert me.png and output a webP file named me.webp.

cwebp me.png -o me.webp  

Note: The command above only works if cwebp is in the same directory as the images, if the images are in a different directory then the path to the directory relative to the location of cwebp needs to be given.

Obviously if you have many images you're not going to want to run a command for each one, we need a way to automate the process. Ant is my tool of choice for automation, I created a new target, shown below, that can take care of converting all the images in the a directory to webP.

<!--  
    Converts all images in the images directory to .webp format.
-->
<target name="-webp">
    <apply executable="${tool.cwebp}" dest="${project.dir}/${images.dir}" parallel="false">
        <srcfile/>
        <arg value="-o"/>
        <targetfile/>
        <fileset dir="${project.dir}/${images.dir}">
            <patternset>
                <include name="*.png"/>
            </patternset>
        </fileset>
        <mapper type="glob" from="*.png" to="*.webp"/>
    </apply>
</target>

The target above is using an Apply task to loop over all the .png files in the images directory and pass them to the cwebp executable (directory path specified in ${tool.cwebp} property). The <mapper> instructs the naming of the <targetfile /> to use the same name as the source file with the .webp extension.

The Numbers

This blog doesn't have many asset images, and the total file size is small causing little affect to the page load speed. The asset images have been compressed and weigh in at around 26.5 KB. The breakdown of the original images is shown below.

  • me.png - 19.1 KB
  • icon-sprite.png - 5 KB
  • icon-date.png - 965 bytes
  • icon-tag.png - 1.3 KB

The webP images weighed in at around 7.6 KB, a 18.9 KB reduction, obviously this isn't to staggering when it comes to file size. However, a 70% reduction is significant, this reduction would have a huge affect on the page load speed if the amount of size of images was larger. The breakdown of the new webP image size is shown below.

  • me.png - 2.5 KB
  • icon-sprite.png - 3.6 KB
  • icon-date.png - 708 bytes
  • icon-tag.png - 857 bytes

Detecting WebP Support

Unfortunately the downside to webP is that it is only supported by Chrome and Opera at the moment. For my blog around 60% of the visitors in the last 30 days have been using Chrome. For the other 40% of visitors, by just showing webP images they would not see any of the images. Obviously this isn't a viable implementation, so I will need to ensure that when the browser doesn't support webP images they're shown the original PNG images.

One of the most popular feature detection libraries is Modernizr. Modernizr works by checking for defined features that are supported by the visiting browser, the results are stored on a JavaScript object (object named Modernizr) and classes are added to the <html> element. To define the features that Modernizr detects a custom build can be configured. The feature needed for detecting webP support is in the non-core detect section under the name of img-webp, ensure that you have this checkbox ticked when generating your custom build of Modernizr.

With Modernizr included in the <head> section, when the browser supports webP the <html> will have the class webp, however if the browser doesn't support webP the class will be no-webp. These classes allows us to control the asset images that are used with CSS, this will be shown later. Also there is now a Modernizr object available in the global namespace that will contain a webp property that holds a boolean indicating if webP is supported.

One thing that needs to be considered when relying on Modernizr, what if they haven't got JavaScript enabled? When using Modernizr you add a no-js CSS class to the <html> element. Modernizr will remove the no-js class and replace it with a js class when JavaScript is enabled allowing Modernizr to run. The sensible assumption I am going to implement is when the browser doesn't have JavaScript webP is not supported so display the original PNG images.

MIME Type

Before getting stuck into the displaying of the images, it is important that you set the MIME type for the .webp extension on the server that is hosting the web page. The MIME type that the .webp should be set to is shown below.

image/webp

WebP with the <img> Element

The only <img> element on the blog that displays an asset font is the image of my ugly face in the header. One of the options I explored was to show the webP or PNG image by default and then switch it dependant on webP support. For example the webP images are displayed by default and then JavaScript is used to change the src to PNG images when webP is not supported. There are two problems with this approach, firstly browsers that don't support webP will make a HTTP request for the webP images and the PNG images. Although we are making improvements for 60% of the visitors, the experience is getting degraded for the other 40%. The second problem is when loading the page it is noticeable that no image is being displayed then one is being loaded in, a flicker occurs when the page loads with a broken image being replaced with a PNG image.

The strategy I went for solved both those issues, only loading the webP or PNG images depending on webP support and broken images aren't visible to the user. To ensure the user is never shown broken images a tiny transparent base64 encoded image is loaded and the URL to the image without extension is set on a data- attribute. The extension not being included means the webP image and PNG image require the same file name on the server, although this could easily be re-factored to handle different file names. When the page has loaded a JavaScript function will loop through all the <img> elements, if the data- attribute is present the src is updated to the original image with the appropriate extension depending on webP support.

So how does this look in code? The HTML markup for the image is displayed below. As you can see the src attribute is using a base64 encoded image, this ensures a HTTP request is not required to show a tiny image. When optimizing performance it is always crucial to keep the number of HTTP requests at a minimum. The data-src attribute stores the URL to the image without the extension, this will be applied to the src attribute when we know if the browser supports webP. When the browser doesn't have JavaScript enabled a <noscript> tag is used to load the .PNG image.

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw"  
    data-src="/images/me" />

<noscript>
    <img src="<?php bloginfo( "template_url" ); ?>/images/me.png" />
</noscript>

Credit goes to Harry Roberts for the use of a base64 transparent 1px gif, inspired by his article Your logo is still an image... and so is mine!

JavaScript handles applying the data-src attribute to the src. The JavaScript code below runs when the page has loaded because at this point we can be assured that Modernizr would have detected if the browser supports webP. The function retrieves an array of all the <img> elements in the DOM. It then loops through the elements checking if the data-src attribute is present. If the attribute isn't present (content images won't have a data-src attribute) then the element is ignored, however if the data-src attribute is present a check to the Modernizr.webp property is made to see whether the .webp or .png extension should be used.

window.onload  = function () {  
    var i, images, imageType;

    /**
     * Gets all the img tags in the DOM.
     */
    images = document.getElementsByTagName('img');

    /**
     * Image type is determined based on webp being supported.
     */
    imageType = (Modernizr.webp) ? '.webp' : '.png';

    /**
     * Loops through the images loaded via the img tag changing the src to load
     * a .png image that is supported by all browsers.
     */
    for(i = 0; i < images.length; i++) {
        if (images[i].hasAttribute('data-src')) {
            images[i].src = images[i].getAttribute('data-src') + imageType;
        }
    }
};

This approach works across all the modern browsers, unfortunately it doesn't work for IE 6, 7 & 8. In IE 8 the image doesn't render when the JavaScript sets the src. This was a weird issue that I couldn't quite work out, when zooming the page would update and the image would display. IE 6 & 7 unfortunately don't support base64 encoded URLs. A small image could be loaded from the server instead of using a base64 encoded URL. However, because I know that IE doesn't support webP, especially the old versions, I decided to make use of the conditional comments for IE to display the PNG image when the IE version is lower than 9. The implementation of this is shown below.

<!--[if gt IE 8]><!-->  
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw" data-src="/images/me"  />

<noscript>
    <img src="/images/me.png"  />
</noscript>
<!--<![endif]-->

<!--[if lt IE 9]>
    <img src="/images/me.png" />
<![endif]-->

The code above will use the webP approach for all browsers except IE 6, 7 & 8, they will display the PNG image instead.

WebP for images loaded via CSS

Determining whether to load a webP or PNG image in CSS is much easier than what is required for the <img> element. Earlier, when explaining how Modernizr works, I mentioned how Modernizr applies CSS classes to the <html> element to indicate which features are supported by the browser. We can use these classes in the selectors to determine whether the webP or PNG image is displayed. So with regards to the calendar icon that is displayed next to the date of a post, the CSS below was the original CSS used to display the icon.

.date-icon {  
    background: url('../images/icon-date.png') no-repeat;
    background-size: 20px 20px;
}

When the date-icon class is applied to an element the date icon will display. To make a webP or PNG image load based on webP support a parent selector is added using the classes applied by Modernizr. The CSS to do this is shown below.

.no-js .date-icon  
.no-webp .date-icon 
{
    background: url('../images/icon-date.png') no-repeat;
    background-size: 20px 20px;
}

.webp .date-icon 
{
    background: url('../images/icon-date.webp') no-repeat;
    background-size: 20px 20px;
}

The PNG image will be used as the background image if there is no JavaScript or webP support, otherwise the webP image is used. Thanks to Modernizr this is a simple approach to ensure that the appropriate image is displayed to the user. You also don't have to worry about HTTP requests for webP and PNG, as only the displayed image type will be loaded.

Conclusion

Although the value of using webP for my blog wouldn't be noticeable, a 70% save of images for a page that has lots of them would have a huge impact on page load speed. It is well known that images make up the majority of bytes during a page load, so anything that can be done to make the size of the images smaller should be considered. With browser support limited, webP involves extra effort at the moment to ensure the user always sees an image. Consideration should weigh up whether the effort required to implement webP gives results that makes it worth it. It is definitely worth getting an idea of savings that could be made by converting images on your website to webP to see how much, if any, the total image size is reduced. Implementing webP with a fall-back at the moment can be tricky hopefully in the future changes to standards will allow us to specify multiple images and the browser will load the appropriate image for the browser. Bruce Lawson has written an article detailing how that would work.

The source for my blog is available on Github if you would like to delve deeper into my implementation of webP. If you have any comments don't hesitate to email or tweet me, always welcome any discussions or suggestions.

Back to Posts

-->