Yes, I’m guilty! I’m guilty of ignoring the web platform and all the extraordinary riches waiting for discovery. For all the lip service the .NET community loves to give towards being performance-minded, it’s a shame we stop at the response leaving the server. No more fellow reader; it’s time we think beyond the server and think about HTML and, in the case of this post, images.
In this post, I’ll show you how to optimize your image delivery using newer HTML features and the differences between the two approaches. We’ll see how and why you may want to use each approach by weighing the pros and cons. Finally, we’ll look at cute puppies because who doesn’t love puppies?! Nobody.
The Humble Image Tag
If you’ve ever written HTML, you’ll almost certainly have used the img
tag. Images are an essential element of most
web pages, yet they can also be the downfall of many. Suboptimal images can drag down your Core Web Vitals, the
metrics used to determine how a user may perceive your site’s performance. Let’s look at a plain old image tag you may
typically see in an HTML response.
<img alt="good boy corgi" src="/img/good-boy@3x.webp">
Nothing outrageous, right? Well, this particular image is sub-optimal for many reasons.
- This image is 3.1 MB and has dimensions of 2000px x 2952px
- The image tag lacks
width
andheight
attributes, so loading the image will cause layout shifts and page redraws - We serve one image for all devices, regardless of pixel density or medium. It’s wasteful.
Let’s see if we can do better.
Descriptive Responsive Image
The img
tag has a few new attributes that can help developers address the above-mentioned issues. Those attributes
are srcset
and sizes
.
The srcset
attribute allows you to define a set of source images the browser should consider when rendering the HTML
to the user. The critical word here is “should”, as the browser determines the selected image. We’ll see how to steer
the browser in a particular direction, but it will always be up to the browser. First, let’s see an example of an image
with a srcset
.
<img
srcset="/img/good-boy@3x.webp 2000w, /img/good-boy@2x.webp 1000w, /img/good-boy@1x.webp 500w"
src="/img/good-boy@1x.webp"
alt="a good boy - corgi puppy"
decoding="async"
>
Three images are defined with their width descriptor specified in <number>w
format. The width descriptor tells the
browser what the image’s intrinsic width is in a unit-agnostic way. This is important as some displays have pixel
densities of 1x, 2x, or 3x. We also set the src
tag as a fallback for clients that do not
support srcset
, although those clients are almost non-existent.
Let’s give the browser hints on what to choose when displaying one of our three options. That’s where the sizes
attribute comes into play.
<img
srcset="/img/good-boy@3x.webp 2000w, /img/good-boy@2x.webp 1000w, /img/good-boy@1x.webp 500w"
sizes="(max-width:500px) 500px, (max-width:1000px) 1000px, (max-width:2000px) 2000px"
src="/img/good-boy@1x.webp"
alt="a good boy - corgi puppy"
decoding="async"
>
Let’s see what the sizes
mean:
- When the maximum width of the viewport is 500px, the image will take up 500px in layout.
- When the maximum width of the viewport is 1000px, the image will take up 1000px in layout.
- When the maximum width of the viewport is 2000px, the image will take up 2000px in layout.
We use CSS to define matches in the form of (max-width:500px)
. Additionally, we could use the use case and assume all
images will always be displayed at 100vw
.
<img
srcset="/img/good-boy@3x.webp 2000w, /img/good-boy@2x.webp 1000w, /img/good-boy@1x.webp 500w"
sizes="100vw"
src="/img/good-boy@1x.webp"
alt="a good boy - corgi puppy"
decoding="async"
>
Again, it’s important to note that the client ultimately decides which option is best given the current state of the viewport.
What are the advantages of this approach?
- The client knows best, so let it choose for you.
- Once the largest-size asset is loaded, it will always be used from the cache. Upgrades never any downgrades in quality.
- Less HTML than the other option.
What are the disadvantages?
- Lack of control and “art direction”
- Black box of when and why things might be chosen.
Let’s look at the next option, the picture
tag, and see how we may overcome some disadvantages.
The Descriptive Approach to Responsive Images
The picture
tag allows us to describe what image to serve and when to serve it. This allows us a level of control not
available to folks using the img
tag with srcset
and sizes
. Let’s take a look at an example.
<picture>
<source media="(max-width: 500px)" width="500" height="738" srcset="/img/sad-boy@1x.webp">
<source media="(max-width: 1000px)" width="1000" height="1476" srcset="/img/sad-boy@2x.webp">
<source media="(min-width: 2000px)" width="2000" height="2952" srcset="/img/sad-boy@3x.webp">
<img src="/img/sad-boy@3x.webp" alt="a sad boy - French bulldog in a hoodie" loading="lazy">
</picture>
As you may notice, it’s more verbose, but we now can choose precisely what image gets displayed and when it gets displayed.
This can give us a sense of art direction not available with the previous method. We could display images optimized for mobile devices or desktops based on media queries. This can help us optimize the size and dimensions of an image like we couldn’t before.
Advantages of this approach?
- Art Direction - Images change as the viewport changes
- Flexibility
- Still delivering the optimal experience.
Disadvantages to this approach?
- Verbose - lots and lots of HTML
- Resizes may cause additional network requests for new assets
Let’s see both of these examples in a demo.
Responsive Images Demo Time
This video is hosted on Mastodon, so you can head to my Mastodon page to see it in action. You can also head to my GitHub repository to download the sample ASP.NET Core project.
As you notice, when I resize the window, the pictures of puppies change (look at the 1x, 2x, and 3x in the corners).
The Corgi’s 1x
image is never displayed, but the French Bulldog’s image behaves more predictably. Very cool, right?
Give it a try by downloading the demo from GitHub.
Conclusion
Responsive images are an excellent feature of HTML, but they require a bit of thought upfront and an understanding of
what you’re attempting to accomplish. The img
tag has srcset
and sizes
and might be an easier path for ASP.NET
Core devs as a starting point, but the picture
tag offers more control and “art direction”. Regardless of your choice,
you’ll deliver an optimized experience to your users.
Thanks for reading and sharing these posts with friends and colleagues. Cheers.