Swapping Images with the Sizes Attribute

Posted by Scott 3/9/2018

Over the years we've designed components that enable users to magnify, swipe, and pan around images in a wide variety of ways. Early on, it was tricky to build these components in ways that used image sizes responsibly, and we'd sometimes end up delivering larger images than we'd prefer–ensuring that the image would at least be sharp across all devices. Thankfully, as "responsive" image standards have gained support we've been able to offload much of that source selection logic to the browser, and in that regard, one particular trick has served us really well that we wanted to share.

Recapping srcset and sizes

If you've worked with the srcset attribute on images, you're probably familiar with its relationship to the sizes attribute. For sake of context here's a quick refresher: the srcset attribute offers a place to list one more more potential source files (paired with their inherent pixel dimensions) for an image, and the sizes attribute gives the browser the rough idea of the dimensions at which the image will be displayed in the page layout. Using these two attributes, the browser can decide which source file should be loaded and displayed at a given viewport size.

Here's an example of an image that uses srcset and sizes:

<img src="imgs/bike.jpg"
     srcset="imgs/bike.jpg 480w, imgs/bike-1200.jpg 1200w, imgs/bike-2000.jpg 2000w" 
     sizes="100vw" 
     alt="A red bicycle">

Above we have 3 potential sizes of the same image at 480px wide, 1200px wide, and 2000px wide (note the w unit representing width). The sizes attribute says the image will be displayed at 100vw (aka 100% of the viewport width), which happens to be the default value for sizes if it was omitted. With this information, the browser will choose a source based on relevant conditions it already knows, like the browser's viewport size and the device's screen resolution (which may act as a multiplier of the viewport size when selecting an image), and even connection speed, typically erring on the side of using an image that is too large over one that isn't large enough.

sizes accepts more complex conditions too: it allows us to specify different image display dimensions depending on media queries. For example, an image can be described as 100vw wide when the viewport is smaller than say, 40em, and then only 600px wide at viewport sizes larger than 40em:

<img src="imgs/bike.jpg"
     srcset="imgs/bike.jpg 480w, imgs/bike-1200.jpg 1200w, imgs/bike-2000.jpg 2000w" 
     sizes="(min-width:40em) 600px, 100vw" 
     alt="A red bicycle">

...and naturally, any number of those conditions can be used to describe more complex layout scenarios.

Letting Sizes Do More Work

One thing that is interesting about sizes is that the browser trusts its description over the actual image's size in the page layout, which may not actually correspond at all. In other words, sizes allows you to lie about an image's layout size, and that turns out to be pretty powerful for dynamic image components like a magnifier tool.

Take the first example again: the sizes attribute is set to 100vw, so if the browser viewport is say, 400px wide, the browser will try to select the smallest available image that is at least 400px wide (imgs/bike.jpg in this case).

<img src="imgs/bike.jpg"
     srcset="imgs/bike.jpg 480w, imgs/bike-1200.jpg 1200w, imgs/bike-2000.jpg 2000w" 
     sizes="100vw" 
     alt="A red bicycle">

But! Given the same viewport conditions, if we were to change the sizes attribute value to 200vw, the browser will select a source that's appropriate to use for an image that's 200% of the viewport width, or 800px wide (400 x 2). The 1200px wide image is closest to that value so it'll use that file instead, even if the image is actually styled at a smaller size in the layout (with CSS).

<img src="imgs/bike.jpg"
     srcset="imgs/bike.jpg 480w, imgs/bike-1200.jpg 1200w, imgs/bike-2000.jpg 2000w" 
     sizes="200vw" 
     alt="A red bicycle">

It's worth mentioning here too that the browser will continuously evaluate an image's source selection throughout the browsing session, since it may need to use a different source if the user resizes their browser, drags the browser window to a higher resolution display, or changes their device orientation (which often resizes the viewport). Also, it will re-evaluate whenever the sizes attribute's value changes, which means we can use JavaScript to do that in real time if the user interface calls for it.

Knowing that, you might see how we could manipulate sizes to swap sources in a magnifier component...

A Quick Demo

We put together some quick demos of a JavaScript-enhanced image magnifier component to show a practical use of this behavior in action. In this first demo, the magnification happens "inline", meaning it zooms the image in-place with a higher resolution version, and it overflows its boundaries in the layout so the user can pan around. The panning behavior works in one of two modes: immediately on mousemove or touch-drag, or with regular overflow scrolling, if you click the zoom button first (which makes keyboard use possible).

In either behavior, the JavaScript swaps the source for a higher resolution image by merely toggling the existing image's sizes attribute to a desired multiple of the image's current width in the page. For example, if the desired image magnification is 5x, and the image is 200px wide when the user attempts to magnify it, the script will toggle the image's sizes attribute value to sizes="1000px", and the browser will find a new appropriate source for the image among the list of srcset sources that was already in the HTML when it first arrived.

After zooming out, the JavaScript returns the sizes attribute to its former value. This may trigger the browser to display the original low resolution image again, but it may just keep the higher quality one in place instead–potentially avoiding a request to a lower quality image that isn't as good as the one already in the page.

For the sake of demonstration, the inline behavior above is just one way to utilize this pattern. Here's a second demo that creates a "loupe" magnifier on mouse hover. In this case, the JavaScript duplicates the original image element to place it in another overlaid element, and it sets that new image's sizes attribute to a multiplied value to get a higher resolution source:

Overall, we find this trick to be really handy, particularly as it allows our clients to place all of their image sizes in one place in the markup, which tends to make integration easier with various content management systems.

So that's one thing you can do to simplify pretty complex image selection with the new sizes attribute. In the near future, we'll likely clean up the code for this demo component and release it in a reusable way, (though, the messy source code is open if you want to poke around).

Thanks for reading!