Responsive Images in a nutshell

Responsive Images in a nutshell


Page load speed is an important metric for SEO. Targeting images for the appropriate browsers goes a great deal in achieving this, particularly on mobile devices. We shall discuss the srcset attribute on the img tag to help us achieve this. This attribute is available on newer browsers.

Before we start, we need to understand that there are two ways to decide what images to serve:

  1. Based on pixel density. This became popular due to “retina” display screens. The “window” element of the browser has a setting called window.devicePixelRatio. This is a double-precision floating-point value indicating the ratio of the display’s resolution in physical pixels to the resolution in CSS pixels. Per CSS standard, 1 CSS px unit is 1/96 of an inch. Alternately, per CSS standard, there are 96 CSS-pixels in 1 inch. A little side-note here. In the past, device-pixels and CSS-pixels used to be the same. With the advent of “retina displays” and “amoled displays”, the actual pixel size in the screen is no more standard. CSS pixel is the unit used by CSS in its “px” units. In iPhone11, for every 1 CSS-pixel, there are 3 actual device pixels. Hence its a 3x devicePixelRatio. In Samsung Galaxy S10, this ratio is 4x (4 actual device pixels for each CSS Pixel). Since devices with various physical pixel densities are here to stay, we can provide those devices with higher-density images and videos, and thus create a sharper view of those media. And yes, it means that in the Samsung Galaxy S10, for example, for an image aimed at CSS resolution of 300 pixels, you can load an image with up to 1200 pixels!
  2. Based on image width. We can choose to serve image based on the image width. This one is most popular as it sort of also includes 1.

Note that when using img attribute, we cannot use use both 1 and 2. I.E., img tag cannot be used with both pixel-density and image-width at the same time. We can only use one or the other. But we shall see that 2 almost includes 1, almost since there might still be edge cases. If we want to have more control over images we will need to use the picture element.

The srcset attribute

Lets start with 1. Here we serve images after detecting the devicePixelDendity. This approach has some traction, like in serving logo images.

<img src="images/my_img.jpg" srcset="images/my_img.jpg 1x, 
images/my_img_2x.jpg 2x, images/my_img_3x.jpg 3x, images/my_img_4x.jpg 4x" alt="my images">

Here I’ve first given a default image for cases where a browser does not support srcset. Default image is the one in the src attribute. srcset then lists each alternate image to be chosen based on the devicePixelRatio. 1x image is one with 96 device pixels per inch. 2x has 96×2=192 device pixels per inch and so on. Samsung Galaxy S11 has device pixel per inch of 502. Hence it will be like 5x approximately.

Now let’s see an example of serving different images based on image width (the no. 2 approach).

<img src="images/my_img.jpg" srcset="images/my_img.jpg 500w, 
images/my_img_2x.jpg 1000w, images/my_img_3x.jpg 1500w" alt="my images">

Here, the browser needs to do a bit of math behind the scenes. Firstly, 500w is the width of image in CSS-pixels. We tell the width of the different image beforehand. Next, the browser also knows the devicePixelDensity (lets say it is 3x). It uses these two info together to choose what image to download. The math goes like this. Browser cycles through the sources in srcset, in order. It first looks at image 500 CSS-pixels wide. Browser knows its viewport dimensions. Say it is 360 CSS pixels. Then it computes the ratio 500/360 = 1.3. This ratio is matched with devicePixelRatio. Since this is less than 3, it goes on. Next one is 1000 CSS pixels wide. So 1000/360 = 2.8. OK, its still less than 3. Next one is 1500 CSS pixels wide. 1500/360 > 3. Browser stops here and chooses the previous one (1000w image). As you can see that browser sort of includes the devicePixelDensity.

Approach 1 is very specific about what image to use for which devicePixelRatio, without depending on browser to compute this for us. Since browsers can compute slightly differently, we can go for 1 if this is our paramount concern. Though in majority of cases, we use 2. This also makes it possible to load different images for different devices (mobile vs desktop). Though for this too, we depend on the browser to choose this for us. If we wanted to explicitly specify which image to serve in desktop vs mobile (via media query), we will need to use picture element. Only a picture element allows us to include media-queries as well when choosing which images to load.

We can add sizes attribute as well to further filter down the image to load. This is more clear when lets say we have in CSS, {width: 50%} applied on the img element. So now, when browser actually downloads an image from server, it hasn’t yet loaded the CSS (browsers load html and images first, before any CSS). So it has no idea that downloaded image will be displayed at 50% of its width (meaning, lower resolution images might still have been OK if browser had this info when downloading images). If nothing is specified, browser is going to assume that image will be displayed at 100% width. So, if we also declare the width that the image is going to be displayed at, browser can choose what image to download more optimally. That is exactly what sizes attribute does.

sizes attribute allows us to add the media queries in the markup and tell the browser exactly how big the image is going to be when shown, for different viewport sizes. Note that here, we moved the width specification from CSS into the markup itself.

Since sizes makes things more complicated, lets understand assuming the devicePixelDensity is 1x.

<img src="images/my_img.jpg" srcset="images/my_img.jpg 500w, 
images/my_img_2x.jpg 1000w, images/my_img_3x.jpg 1500w" 
sizes="(min-width: 1200px) 50vw, (min-width: 760px) 75vw, 100vw" alt="my images">

The last option without any media queries (100vw) is the default which will be applied when none of the other media queries evaluate to true. “vw” is percentage layout. “w” is exact CSS-pixel dimension. So 500w means 500px, just within the srcset and sizes markup, we instead represent px as w.

So, here, when viewport size is 1200px and higher, the image is going to be displayed at 50% width of viewport and the image width can be 0.5×1200 = 600w or smaller. Since my_img.jpg is 500w, it will get downloaded. If there was no sizes attribute, 1000w image would have been downloaded.