- Breakpoints should be determined by your content and not device-specific widths
- Define image dimensions in relation to breakpoints
- Calculate pixel density factors and add these to your image list
- Consolidate image list to a more manageable length by eliminating values that are too close
- Leverage automation when outputting your various image sizes
- Utilize the srcset and sizes attributes to switch out a different source of the same image in order to respond to size and pixel density factors
- Picturefill allows us to leverage the power of these attributes today
- Although the polyfill has some potentially downsides, they are outweighed by the benefits
The first step is to define the image dimensions and related breakpoint of each image which you plan to apply a responsive image solution to. A working prototype of your webpage with actual content is needed to accurately identify this information. It is important to note that your breakpoints should be determined by your content, not device-specific widths. The reason for this is because the device landscape is constantly changing; therefore, today’s popular device dimensions are always in flux. By letting the content determine where breakpoints will take effect, we are ensuring that our interfaces look and function on any screen instead of just a few arbitrary displays.
*Note: When determining which images qualify for a responsive image solution, please remember that the most appropriate images are those that are in the content, i.e. the images that are part of the HTML, and not images in which the background is defined in CSS.
Start by scaling your browser window out to it’s maximum size (or to the point at which you have defined a max-width to constrain the overall width of your content). Then, document the max-width of your content and the width of the image as it appears on the screen using developer tools or a similar browser extension.
Next, scale the browser window down until you reach the next breakpoint that affects the image. At this point, you should document both the breakpoint value and the width of the image. Continue scaling down and documenting each breakpoint and the corresponding width of the image until there are no longer breakpoints that will affect the image. When you are done, you should end up with a list similar to this for each image (with your values of course):
- [max-width] : [image width]
- [breakpoint large] : [image width]
- [breakpoint medium] : [image width]
- [breakpoint small] : [image width]
*An important note regarding breakpoints: the more breakpoints you have, the harder the code will be to maintain. In addition to this, an excessive amount of breakpoints could result in bloated CSS. Therefore, its best to keep these at a minimum and consolidate them wherever possible.
The next step is to calculate the width of each image multiplied by the pixel density factors that you plan to support. Deciding on what range of pixel density factors that should be supported can be difficult not only because there are so many (and the list is growing), but support for each multiplier will introduce an array of additional images that you must create and define in your responsive image solution. The best thing to do here is to be pragmatic and make a decision based on what’s best for your users.
Our list should look similar to the following once we have calculated our supported pixel density factors:
- [max-width] : [image width], [image width x1.5], [image width x2]
- [breakpoint large] : [image width], [image width x1.5], [image width x2]
- [breakpoint medium] : [image width], [image width x1.5], [image width x2]
- [breakpoint small] : [image width], [image width x1.5], [image width x2]
As you can see, our image size list is growing and will get even larger with more pixel density factors and breakpoints. It makes sense at this point to consolidate the list to a more manageable set of values, e.g. any values that are the same or do not have a difference of more than ~200px. When there are two values that are less than this amount apart, eliminate the lesser value. Here is an example list that clearly illustrates our values once it has been consolidated:
- (min-width:1280px) : 1040px,
- (min-width:1120px) :
924px, 1386px, 1848px
- (min-width:800px) : 800px,
- (min-width:400px) : 400px, 600px,
*Note: I am using pixel values for the breakpoints in the example above to clarify the correlation to image width, but in practice your breakpoints should be relative units.
Now that we have a consolidated list of image widths, the next step is to export each image with a naming convention that identifies both the breakpoint and the multiplier; for example: I have nicknamed my next to largest breakpoint ‘large’ and the multiplier is 2x, so my file name will be ‘firstname.lastname@example.org’. I like to save images for web from Photoshop at ~70% quality to ensure maximum optimization without losing image quality (this will vary based on the image — the goal here is to find lowest quality setting without degrading the finer details of the image). If you are saving as JPEG, be sure that the image is visible as soon as possible for the user by selecting the ‘progressive’ image format.
One method to save out varying image sizes is to use predefined actions that will automatically save your image to the sizes you need. This sort of automation can easily be done in your design program of choice. Unfortunately you will still need to manually rename each image with the relevant breakpoint name and modifier. If you use Photoshop, here is a guide to setting up your own actions in Photoshop, or of course you can utilize predefined actions.
Finally, its important to remember to optimize your images before implementing them into your page. This will ensure that any image data that can be removed or simplified is done, resulting in significantly smaller image files. Similar to the process of resizing images, there are multiple ways to handle this task: you can manually optimize images via an app or console tool, or you can leverage a build tool to automate this task. My favorite image optimization apps are ImageOptim for JPGs and ImageAlpha for PNGs, but there are several others to choose from. Alternatively, you could leverage the power of automation via a build tool, which is great because you don’t have to remember to optimize — you simply configure the tool to do this for you every time there is a new image in your project. I like to use the Gulp plugin imagemin, which can also optimize SVG and GIF files.
The final step is implementation. Our use case will be what’s known as ‘resolution switching’ because we are simply switching out a different source of the same image in order to respond to size and pixel density. Therefore, we will make use of the the srcset and sizes attributes, which are part of the picture specification. These attributes extend the <img> and <source> elements to provide a list of available image sources and their sizes in which the browser can then use to pick the best image source. We are essentially providing the browser the information we do know, and letting it determine the most appropriate image in response to factors that are unknown, e.g. pixel density of the user’s device, bandwidth, etc.
Let’s begin with srcset. First, we will provide a fallback image in the ‘src’ attribute in the case the browser doesn’t understand the srcset attribute — an image that’s roughly in the middle of your image list is sufficient. Next, we will target the <img> tag that will contain our image information and add the ’srcset’ attribute. Then we will list each image from smallest to largest in a comma-separated list, with each image source followed by the width of the image that we documented in the previous step and followed by the width descriptor (‘w’). The result should look similar to the following (with your values of course):
<img src="image_medium.jpg" srcset="image_small.jpg 400w, email@example.com 600w, image_medium.jpg 800w, image_xlarge.jpg 1040w, firstname.lastname@example.org 1386w, email@example.com 1600w, firstname.lastname@example.org 1848w, email@example.com 2080w" alt="Image description" />
Now we have a image with a list of available sources, each with information that the browser can use to determine the best choice, along with a fallback image and alternative text. If srcset is supported, the browser will download only the image it calculates is most appropriate for the user’s device, otherwise it will load the fallback image. Thus, the chances of wasted bandwidth and the resulting page bloat is significantly reduced.
*Note: there is also a ‘resolution’ descriptor available to the srcset attribute, which allows you to define alternative image sources based on pixel density factors. While this simplifies the amount of sources you have to define, it will limit the options for the browser. Also note that the width and resolution descriptor do not mix, so you must chose one or the other.
We can provide the browser with additional information to help it determine the best image by also leveraging the sizes attribute. This isn’t completely necessary for srcset to work — the browser will still parse though the information provided in the srcset attribute and select an image, but it will assume that the image is suppose to take up the full width of the viewport. This can obviously lead to unwanted results, so its probably best to provide some information here:
<img src="image_medium.jpg" sizes ="(min-width:1120px) 924px, (min-width:1280px) 1040px, 100vw" srcset="image_small.jpg 400w, firstname.lastname@example.org 600w, image_medium.jpg 800w, image_xlarge.jpg 1040w, email@example.com 1386w, firstname.lastname@example.org 1600w, email@example.com 1848w, firstname.lastname@example.org 2080w" alt="Image description" />
The above example is telling the browser that at a minimum-width of 1120px the image should be 924px wide, and at a minimum-width of 1280px the image should be 1040px wide. Otherwise, the image should take up 100% of the browser window.
The browser will then use this information to further aid it in selecting the appropriate image source for the user. You can get as detailed as you want here by listing each breakpoint and the corresponding width of image (which can obviously result in duplicated layout info), or you can keep things relatively generic. The point is the more information the browser has, the better it can determine the right image source.
Art-Direction Use Case
The srcset and sizes attributes will cover most use cases. Alternatively, there is the possibility that you need to modify the images, e.g. crop images differently at each size, which is known as the art direction use case. If each version of an image needs to be different, you will want to use the <picture> element, which is part of the same specification as srcset. Which responsive image method you chose depends entirely on the use case. To learn more, read Jason Grigsby’s article explaining the details.
Browser support for the srcset and sizes attributes is gaining traction but for most projects you will need to go a bit deeper. Luckily, there is an excellent polyfill called Picturefill which provides cross-browser support for the <picture> element and associated features (this includes srcset). This polyfill allows authors to use the recommended picture syntax, therefore enabling them to remove the polyfill once deeper browser support is available. Although the polyfill has some potentially downsides, they are outweighed by the benefits.
*Note: When using picturefill, it is recommended that you leave off the ’src’ attribute in order to prevent some users from experiencing double-downloads.