The next generation webp format is one of the most important image formats to have come along in the last several years.

My first impression of webp images was 'meh'. I read articles on the next generation webp image format. The articles claim that webp images are about 26% smaller than their png equivalents. What I read was impressive, but not just enough to delve into learning how to use them.

I only started playing around with webp to chase a better Google lighthouse score. Google knocked my 'Best Practices' score down a couple of notches, and I finally decided to investigate using them. I'm glad that I did! I did not find a 26% improvement with my own images, but a 95% improvement in size than the identical .png images that I had created. Want proof? I'll demonstrate my own results...

Please take a look at my Greensock animation introducing the next version of Galaxie Blog at https://gregoryalexander.com/blog/index.cfm/2019/9/16/Galaxie-Blog-135-Animated-Blurb. If you're using a modern browser, you should notice that this intense graphical scene renders rather quickly. On a vain note- I also hope that you find the scollmagic scene to be pretty...

The scrollmagic scene that you are looking at should deliver images in the webp format if your browser supports them. If the browser does not support webp, png's will be rendered instead. I'll go over the code later on, but right now I will compare the image sizes to show you the improvements. I'll provide the link and size of each actual image in the grid below so you can take a look yourself.

I created the original source images using Adobe Illustrator, and exported them to png's using the 'save as legacy web' option. To create the web images, I took the png images that I had created and used Irfanview to export the png as a webp. Take a look at the size reduction in the table below.

The entire payload of all of the combined webp images is 90% less than one of the actual png images! With this type of extreme size reduction we can vastly improve our loading time, or we can now use full quality images in animated sequences like this!

I am also using webp instead of jpg images. Here the improvements are not as as substantial, but I have shaved about 10-15% of the size of the jpg images.

How to get started with webp images.

Server side code

Currently, with the exception of Safari- most modern HTML5 capable browsers support webp images. To determine if a browser supports the next generation format is relatively easy. Thankfully, nearly all modern browsers will include webp support in the header if the browser supports the webp next gen image. Here is the ColdFusion script that I developed.

view plain about
1<!--- Determine if the http accept header contains webp. The getHttpRequestData().headers is a structure and we are targetting the accept element in the array. Note: nearly all modern browsers will include this if the browser supports the webp next gen image. --->
2<cfset acceptHeader = getHttpRequestData().headers["accept"]>
3<!--- Does the header accept webp? --->
4<cfif findNoCase("webp", acceptHeader) gt 0>
5    <cfset clientAcceptsWebP = true>
6<cfelse>
7    <cfset clientAcceptsWebP = false>
8</cfif>

A hybrid server side and client side approach

Most web servers support the webp image format as well. However, on web servers that are not current, you may need to add the webp mime type. Consult with your ISP or server documentation to find out how to add webp mime type on your server. Here is the function that I developed to test webp support on the server and save the result as a persistent application variable. You will have to create a small webp image and upload to your own server for this to work (see headerBodyDivider.webp below).

view plain about
1<!--- Determine if the webP mime type is set up on the server. --->
2    <cffunction name="serverSupportsWebP" access="public" returntype="boolean" output="yes">
3        <!--- Note: we need to eliminate https from the root URL if it exists. I ran into errors trying this with https (a cryptic certificate error). --->
4        <cfset thisUrl = replaceNoCase(application.rootUrl, "https", "http")>
5        <!--- The headerBodyDivider image is a tiny .webp image (around 1k). We are going to read this, and if it was found and the mime type is correct, we will assumed that the mime type is correct. Otherwise, we will determine that the server does not support the webp mime type. --->
6        <cfhttp method="get" URL="#thisUrl#/images/divider/headerBodyDivider.webp">
7            
8        <!--- Was the webp image found? --->
9        <cfif cfhttp.mimeType contains 'webp'>
10            <cfset webp = true>
11        <cfelse>
12            <cfset webp = false>
13        </cfif>
14        <!--- Return it. --->
15        <cfreturn webp>
16    </cffunction>

In my code, I either use the server side logic above to test both the server and the client for webp support, or I use a hybrid approach where I save the result of the serverSupportsWebP server side function and test for support using javascript on the client like so:

view plain about
1<script>
2        // WebP support detection. Revised a script found on stack overflow: https://stackoverflow.com/questions/5573096/detecting-webp-support. It is the quickest loading script to determine webP that I have found so far.
3        function webPImageSupport() {
4            // Detemine if the webp mime type is on the server. This is saved as a ColdFusion application variable.
5            var serverSupportsWebP = <cfoutput>#application.serverSupportsWebP#</cfoutput>;
6         var elem = document.createElement('canvas');
7            
8         if (serverSupportsWebP && !!(elem.getContext && elem.getContext('2d'))) {
9         // Is able to get WebP representation?
10         return elem.toDataURL('image/webp').indexOf('data:image/webp') == 0;
11         }
12         // webp is not supported on older browsers such as IE.
13         return false;
14        }
15    </script>

Using either approach is valid. However, I typically use server side code to set the paths of my images, and only use the hybrid approach (using the script above) when I am manipulating the DOM with javascript.

Example server side logic to set the image extension

I personally set the default image to be png or jpg, and only change it if both the client and the server support webp:

view plain about
1<!--- Determine if the http accept header contains webp. The getHttpRequestData().headers is a structure and we are targetting the accept element in the array. Note: nearly all modern browsers will include this if the browser supports the webp next gen image. --->
2    <cfset acceptHeader = getHttpRequestData().headers["accept"]>
3    <!--- Does the header accept webp? --->
4    <cfif findNoCase("webp", acceptHeader) gt 0>
5        <cfset clientAcceptsWebP = true>
6    <cfelse>
7        <cfset clientAcceptsWebP = false>
8    </cfif>
9        
10    <!--- Logic to determine if the server has the necessary webp mime type was done in the application.cfc template. We will use the application.serverSupportsWebP variable that the mime type is installed on the server. Of course, both the client and the server need to support webp images before we can deliver them.--->
11    <cfif application.serverSupportsWebP and clientAcceptsWebP>
12        <cfset webpImageSupported = true>
13    <cfelse>
14        <cfset webpImageSupported = false>
15    </cfif>
16        
17    <cfif webpImageSupported>
18        <!--- Overwrite the headerBodyDividerImage var and change the extension to .webp --->
19        <cfset headerBodyDividerImage = replaceNoCase(headerBodyDividerImage, '.png', '.webp')>
20    </cfif>

Tips

When saving webp images, don't use the Adobe Photoshop or Illustrator plugin's. There are two of them as of today, and neither of them work well. Instead, just create your images and save them as jpg or png, and use Irfanview 64 to open and save them as a webp image. I have tried many approaches, and this method has proven to be the most reliable.

Thanks for reading, and happy coding!

Gregory