Back to All Posts

It's Never Been Easier to Performantly Put Images on the Web

The web has made many significant, meaningful advancements in performantly loading and rendering images over the years. Let's reminisce about a few.

It probably wasn't in your local news, but something non-trivial in web development occurred early this year: Microsoft Edge added support for the AVIF image format. It was the last of the major browsers to do so, after taking a frustratingly long while, apparently due to licensing issues.

Nevertheless, I'm so here for it. I've been waiting a long time to pull the trigger on supporting AVIF in PicPerf, so I'm very happy to finally make that happen.

Excitement aside, the news also gave me some pause. The web's made some big strides in enabling the effective, performant use of images over the past decade or so, and this is just the latest example of that trend. It's worth reminiscing. Here are some that stick out to me.

Responsive Images

For a long time, the HTML <img> tag had one job, and it didn't behave differently based on the device on which it was used. The src attribute pointed to an image, and always that image. As the variety of screen dimensions exploded, that obviously became a problem. Image are expensive resources, new formats were beginning to be introduced, and the "one size fits all" src attribute just wasn't cutting it.

So, in 2014, browsers began rolling out the srcset attribute for <img> elements, and a new standalone element: <picture>. Suddenly, you could begin loading completely different image resources depending on the size of the user's device, it's rendered dimensions, and the screen's pixel density:

<img src="small.jpg" 
     srcset="small.jpg 500w,
             medium.jpg 1000w,
             large.jpg 2000w"
     sizes="(max-width: 600px) 500px,
            (max-width: 1000px) 1000px,
            2000px"
     alt="Example Image">

And using the <picture> element, you could now much more easily load modern image formats for devices that supported them, and fall back to an "older" version for those that didn't:

<picture>
  <source srcset="image.avif" type="image/avif">
  <img src="image.jpg" alt="image description">
</picture>

I also appreciate how progressive enhancement is a high concern of these tools. If used right, nobody's experience had to break just because they were on a company machine using a version of Internet Explorer from 1942.

Native Lazy Loading

Years ago, I wrote about rolling your own image lazy loading in WordPress using JavaScript (specifically the Intersection Observer API). As suggested by my blog traffic at the time, it served a need. People knew just how overbearing loading images unnecessarily could be, and it was common to use JavaScript to "trick" the browser into loading the resource only when it was needed by modifying the src attribute (glance at the post if you're unfamiliar with the tactic).

Nevertheless, it was still a trick, and when there was chatter of Chrome introducing native lazy loading back in 2018, you could feel a collective sigh of relief amongst the front-end community. There'd be much less "thinking" about it all. Add the attribute, and it just works. No JavaScript needed:

<!-- The old way: reliance on JavaScript. -->
<img data-src="below-the-fold-image.jpg" alt="my image" />
<script>jsThatTurnsDataSrcToSrcWhenImageIsInView();</script>

<!-- The new way: just use the "loading" attribute! -->
<img loading="lazy" src="below-the-fold-image.jpg" alt="my image" />

Of course, there are still footguns with the tool too. For example, you can actually end up harming performance by lazy loading everything. This insight even led to WordPress making a an image loading change a while back – moving from lazy loading everything to everything except the first image in a block theme.

Priority Hints

The browser is a smart work of engineering, and it's really good about prioritizing the loading of different resources at the right time. Still, it can benefit from receiving explicit "hints" when it couldn't otherwise tell. This is why priority hints can be helpful.

You can clue the browser in on resource priority in a number of ways, such as preloading fonts in the <head> of your document, setting the priority of a fetch() request, and with the fetchpriority attribute, it's possible with images too:

<img src="cat-1.jpeg" fetchpriority="high"/>

This is probably most applicable for your "hero" inage appearing above the fold. If it's the bulk of the content a user will see when first visiting your page, give the browser no doubt: this image is important. It's particularly useful in combination with native lazy loading. Most of your images might be safely fetched as needed, with the exception of the primary one at the top of the page:

<img src="cat-1.jpg" fetchpriority="high" />
<img src="cat-2.jpg" loading="lazy" />
<img src="cat-3.jpg" loading="lazy"/>

Again, a nice thing about these tools is that they won't break the experience for anyone on an older device. Those attributes will just be ignored.

Modern Formats

It's worth camping out on this one a little more. For a long time, formats like PNG and JPEG have dominated the web, and they've served us well during that time. But formats like WebP and AVIF, in many (probably most) cases, serve as superior alternatives.

Narrowing in on WebP: this format came onto the scene in 2010 and is based on the VP8 video codec, using "predictive coding" to encode pixels. For both lossless and lossy formats, it's roughly 25-34% smaller in output compared to JPEGs, and it also supports features like transparency and animations. But it's not slways perfect. smaller sizes aren't guaranteed, and lossy compression can sometimes produce lower quality than you might expect.

AVIF, on the other hand, touts even more efficient compression levels – up to 50% smaller than JPEG counterparts. And it also supports features like HDR, arguably making it a better format for larger images where detail preservation really matters.

There are trade-offs, though. Producing an AVIF image takes a rather large amount of computational power – an issue I found really challenging while bringing support to PicPerf. And it also doesn't enjoy the same level of browser support as WebP (that's quickly changing, however).

All that said, I think there's plenty of room for multiple modern formats (we'll soon be talking about JPEG XL a lot more too), and the variety can help serve compression needs that depend on device, image size, and a host of other factors. The web's gonna continue to benefit from that flexibility.

Don't Get Carried Away

The HTTP Archive shows that over 700KB of images show up on your average web page. That's a lot, and if anything, I expect that number to increase in the age of AI-generated media. So, I'm eager to see the tools mentioned here continue to gain adoption as the web evolves. We certainly need them.

Still, it's important not to abuse these or any other tools that might emerge, and we gotta be careful to avoid subconsciously making ourselves believe we can use even more large imagery without consequence. If we fall into that trap, we'll only have modernized the problem, rather than done anything to build better experiences.

On the upside, front-end performance is in the spotlight more than ever before, and we have a ton of great resources to hold us accountable (ex: Core Web Vitals).

The future is bright!


Alex MacArthur is a software engineer working for Dave Ramsey in Nashville-ish, TN.
Soli Deo gloria.

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.
Leave a Free Comment

0 comments