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.
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:
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.
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 imagemin, to see how good these "retro" formats could get.
The results are pretty dramatic:
Optimized .png(using imagemin)
Optimized .jpg(using imagemin)
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!
.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
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:
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:
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.
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:
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! 🤞🏻)
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.
Google has created a suite of tools to help us work with webp files. One of those tools is cwebp, 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 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.
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:
We can use ImgWithFallback very similarly to how we'd use an img tag:
If you use styled-components or Emotion, you may be used to wrapping images in a styled wrapper:
Thankfully, this still works with our ImgWithFallback component. We can wrap it like any other component:
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 docs for more info.
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.
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.
Of course, it doesn't seem practical to manually convert every image I use to webp. I'm already investigating how I can generate these images automatically from the source jpg and png files. Ideally, this isn't something I should ever have to think about, it should happen automatically when I build my site. Expect to see something on that soon =)