In The Cost of JavascriptThe Cost of Javascript, Addy makes a really good point: 200kb of Javascript is more "expensive" than 200kb of images, because the browser needs to do more work to use code compared to images. From the article:

A JPEG image needs to be decoded, rasterized, and painted on the screen. A JavaScript bundle needs to be downloaded and then parsed, compiled, executed —and there are a number of other steps that an engine needs to complete. Just be aware that these costs are not quite equivalent.

This is still very true, but it's a little less significant at this exact moment in history.

With a pandemic sweeping across the globe, I've found that my internet has gotten pretty choppy. Fortunately, because Site Reliability Engineers are both brilliant and tireless, most of the internet is still up and running, but there's definitely something going on—I have a 100mbps connection, but it feels more like 3G at the moment.

This shifts the calculation a little bit. Our devices can still parse and compile javascript at the same speed they could a couple weeks back, but network speeds have gotten slower. So the raw number of bits over the wire is super important right now!

And sites typically have way more than 200kb worth of images; it's not uncommon for a page to have several megabytes of images. Many developers (myself included!) tend not to think about media size much at all.

Happily, there's some pretty low-hanging fruit! In this tutorial, we'll see how we can leverage "next-gen" image formats like WebP. These images are often 2-3x smaller than the legacy formats we know and love (jpg, png). It can make a huge difference.

Watch the video NEW Prefer your lessons in video format? Watch for free on egghead:

Meet the cast

There are three formats that we can use:

JPEG 2000 — an iterative improvement on jpgs. Developed in 1997 primarily for use in film and medical imaging. Allows images to be compressed further, with less artifacts.

JPEG XR — A cousin of jpeg2000 , developed by Microsoft in 2009

Webp — a format developed for the web by Google in 2010, focused on using advanced optimization techniques to reduce file-size. Supports transparency and even animation.

We'll spend most of our time today talking about webp , but we'll revisit the jpeg cousins when we discuss browser compatibility.

How big of a difference does it make?

A few months ago, I used this image in a post:

I did some experiments, using both jpg and png for the source image. I optimized them using imageminimagemin, to see how good these "retro" formats could get.

The results are pretty dramatic:

Details Original WebP .png (from Photoshop) 742 kb 61 kb!92 % smaller Optimized .png (using imagemin) 178 kb 58 kb!67 % smaller .jpg (from Photoshop) 242 kb 50 kb!79 % smaller Optimized .jpg (using imagemin) 151 kb 50 kb!67 % smaller

I've tested it on a lot of images, and it almost always produces files that are 30-70% smaller than even the optimized images!

What about SVGs?I haven't tested this on SVGs at all. SVG is a vector format, meaning that it's made up of mathematical instructions rather than individual pixel colors. It would be a shame to lose the scaling benefits of a vector format, and I suspect it would actually increase the file size in most cases.

Browser compatibility

.webp enjoys support in most browsers:

Critically, we're missing Safari and Internet Explorer.

How about JPEG 2000?

Alright, so we've filled in Safari, but there's still that pesky Internet Explorer…

We've hit caniuse bingo! With these 3 image formats, we have perfect coverage across the browser spectrum. *

Let's look at how we pick and choose different formats for different browsers

'picture' to the rescue!

HTML has two image media elements: the international pop-star img , and the niche hipster artist picture . *

picture is a much newer addition to the language. Its main goal is to let us load different sources depending on resolution or support for a given image format.

Here's what it looks like:

html

The picture tag supports a bunch of source children. The browser parses the source elements in sequence, looking for the first one it can use based on the type . When it finds one, it works out where the image lives via srcset , and swaps it into the img 's src

srcset can do a lot of complicated things, but happily for our usecase, we can treat it the same as src . Essentially, source is config, and it plugs the matching value into the img .

In Chrome, for example, we wind up with something more-or-less equivalent to this:

html

This cascade of sources means that one will match on every browser: Most browsers will use webp , Safari will use jp2 , and IE will use jxr .

But Internet Explorer doesn't support picture !The picture element is too modern a feature for Internet Explorer, and yet this code snippet still works as intended 😮



This is because when the browser hits an element it doesn't understand, it treats it as a div . So what we wind up with is a bunch of divs, and an <img> tag that points at the jxr image, which just so happens to be the format that Internet Explorer understands!

A lazier alternative

The snippet above excels in its ability to match every possible browser with a modern "next-gen" image format. But it assumes that these images exist in these formats.

If we're creating these images by hand, it's a lot of manual labor. And if we're generating them automatically, it can significantly lengthen our build time; image processing is notoriously slow when done at scale.

On my own blog, which receives very little Internet Explorer traffic * , I've opted for a lazier solution:

html

I'm serving the nice and tiny webp to browsers that support it (Chrome, Firefox, Edge), and falling back to a legacy jpg for browsers that don't (IE, Safari).

To me, this is an example of progressive enhancement. Everything still works on legacy browsers, but images will be a bit slower to load. This is a trade-off I am alright with.

(Hopefully Apple will get on this train soon though! 🤞🏻)

Testing that it works

The browser devtools will always think that the image has whatever src you gave it initially. If you inspect it in the elements pane, you'll see that it uses a .jpg .

To check if it's actually working, the best trick I've found is to right-click and "Save as…". On Chrome, you should get a "Google WebP" file format, whereas on Safari or IE you should get a "JPEG".

You can also check the network tab, to see which was actually downloaded.

Converting images to webp

Google has created a suite of tools to help us work with webp files. One of those tools is cwebpcwebp, which lets us convert other image formats to webp.

If you're on MacOS, you can install the suite with Homebrew:

On other platforms, I believe you'll need to download the appropriate libwebp packagelibwebp package from their repository.

once installed, you can use it like this:

-q 80 is a flag to set the "quality", from 1 (worst) to 100 (best). You can experiment with different values. I've found that 70-80 is the sweet spot.

cereal.png is the path to the input file you want to convert.

-o cereal.webp is output path.

Spending time doing this manually is nobody's idea of a good time. In a subsequent post, I'll be exploring different strategies for automating this process, so that we never have to think about this and get all the benefits for free!



If this interests you, be sure to subscribe be sure to subscribe so you don't miss it.

Abstraction with React

A component is a brilliant way to abstract over some of the funkiness with the <picture> element. Here's what I've been using, to glorious effect:

jsx

We can use ImgWithFallback very similarly to how we'd use an img tag:

jsx

Usage with styled components

If you use styled-components or Emotion, you may be used to wrapping images in a styled wrapper:

jsx

Thankfully, this still works with our ImgWithFallback component. We can wrap it like any other component:

jsx

The reason this works is because of how the styled helper operates. It generates a class and injects it into the document's stylesheet, and then passes the generated class name down as a prop: <ImgWithFallback className="sc-some-generated-thing" />



We're delegating all properties to the child img tag, so the right styles still make it to the image. Joyfully, everything works like you'd expect.

Gatsby Image

If you're developing with Gatsby, the gatsby-image package already does a bunch of optimizations out of the box, including converting to webp (though you need to opt in for it).

Gatsby Image isn't meant as a drop-in replacement for img ; it can be a bit more friction to use, but it also comes with a lot of additional magic tricks for your trouble.

Check out the docsthe docs for more info.

Downsides

The only real downside I've found so far is that webp is an annoying format to work with as a user.

Most desktop software doesn't yet support it; I can't open it in Preview on MacOS, for example. This means if I right-click and "Save as…" a webp image, I won't be able to view it!

Converting a webp to a jpg is relatively painless, and a google search turns up many online providers that will do it for free. But still, it's an additional bit of friction. If your site/app encourages users to download images, you might not want to make this switch.

Next steps

I'm pretty happy to have cut the size of images on my blog by ~50%. In addition to the benefits to user experience at a critical time, I'm also expecting that this'll save me some money in terms of bandwidth.