Providing the optimal experience for the user while using images on a page can really suck. There are many problems with serving images to a user that can make the browsing experience slow and bandwidth-heavy.
The Old way
Before responsive design was considered important, embedding an image with HTML was a simple as
<img src="image.jpg" />
This could be done as a CSS background as well however, neither are really performant or reasonable options anymore for several reasons:
- Devices with a higher DPI will render the image by scaling it and it won’t look good.
- Devices with smaller screens are forced to download the same images even if they can’t render them at the native size which results in significant overhead and suboptimal rendering.
- Network performance is a huge variable regardless of device and devices with slower connections are forced to download large images regardless of what they might actually need to initially render the page.
There were many different solutions to these problems depending on the needs of the application but usually they were overcome with a combination of JS and CSS. You could generate a series of images and assign them to some attribute on an element and some JS and CSS would resize the container and trigger the appropriate image to be downloaded.
These solutions were better but they still had their own problems:
- They depended on JS which means that they are naturally going to be slower than any native behavior could be.
- JS doesn’t have any way of accuratley gauging network speed to make informed decisions when downloading content.
- The rendering was either blocking or happened too late in the execution order and resulted in flashes of unstyled content.
The New Way
In the most basic form, the example from above can be changed from this:
<img src="image.jpg" />
<img srcset="image.jpg" />
and browsers (that support
srcset) will be using the new attribute. However, this isn’t very useful as there is no difference in behavior.
srcset can be used to provide the browser with a list of images to be used in different circumstances. There are two ways to tell the browser what those circumstances are. The first by specifying the actual image width, and the second by specifying the pixel density that the image is supposed to be supporting.
For example, supposed you have an image that has a fixed size on the page of 100×100 but you want it to look good with a screen that has a 2x pixel density. You would save out the image in two sizes: 100×100 and 200×200 and then can be used with
srcset like this:
<img srcset="image_1x.jpg 1x,image_2x.jpg 2x" />
This tells the browser to use
image_1x.jpg on devices with 1x pixel density and
image_2x.jpg on devices with a 2x pixel density. Easy, right?
Now let’s say that the same image is rendered at different sizes depending on the size of the screen. Again, same exact image but it scales up or down depending on screen size. The browser needs to be able to determine which image to serve at the given screen size. Well, the pixel density method above doesn’t really do the browser much good as it doesn’t know the actual sizes of the images and therefore doesn’t know which one would be appropriate to render at the given screen size. This is where the second method of supplying images to
srcset comes in.
Using the same images from the exampe above we can change it to the following:
<img srcset="image_1x.jpg 100w,image_2x.jpg 200w" />
This is an improvement to the situation but the browser still doesn’t know the size the image will actually be rendered at on the page until after it has already needed to download an image file. This is where
sizes comes in.
sizes allows you to tell the browser what size the image will actually render as at different sizes. The size of the screen is specified using media queries.
Building on the previous example, lets say that our 100×100 image is rendered at 100% of the viewport’s width at and between 320px and 479px. Also, let’s say that our 200×200 image will display at 200px from 480px and above. We can specify that like this:
<img srcset="image_1x.jpg 100w,image_2x.jpg 200w" sizes="(max-width:479px) 100vw,(min-width:780px) 200px"/>
This will allow the browser to choose an image to download based on the device’s width. Hurray!
It is important for me to note that this feature is still very new as of this post and needs to be polyfilled for older browsers if the behavior is desired. If you do not want to polyfill the feature, you can specify the
srcattribute and older browsers will still have something to render. It is very important to understand that this will cause all browsers to download the
srcimage regardless of whether it is needed and regardless of the polyfill used. This is due to the order in which browsers render a page.