Implementing Responsive Images

Ever since we started using CSS Media Queries to implement Responsive Web Designs, content images have been a problem. Although we could prevent them from messing up the whole page layout with max-width: 100%, mobile users often had to load images that were much bigger than they needed to be. This became even worse when the iPad 3 brought us a big screen with 2x resolution. It required every single image to be twice as big1 to look fine on its high-dpi screen.

There were a lot of creative solutions for that problem. Some images could be moved to CSS where they could be swapped based on viewport width and pixel density. Others could be embedded as vector graphics (svg or webfont). For the remaining images there were dozens of possible solutions, all JavaScript based and all with their own pile of problems. In the meantime W3C couldn’t really decide what to do.

Now they have. Which is why there have been a lot of articles recently about the new browser capabilities related to Responsive Images. However I think that these articles stop at a point where it gets interesting: The actual implementation of Responsive Images.

Browser Decision or Art Direction?

The W3C specified two possible solutions for the problem: srcset and <picture>.

The srcset attribute provides alternative image files to the browser which then chooses the best option based on viewport size and pixel density. That decision process could also include additional factors like connection speed in the future.

<img  
    src="medium.png"
    srcset="
        small.png 480w,
        medium.png 720w,
        big.png 1020w
    "
    sizes="
        (min-width: 1100px) 1020px,
        (min-width: 720px) 720px,
        100vw
    "
    alt="responsive image"
/>

The <picture> tag gives the designer full control over the output. What can be tempting at first is in most cases a lot of work. Good usecases for the <picture> tag would be

  • The website provides different aspect ratios for the same image depending on the viewport size, e. g. 16:9 on desktop and 1:1 on mobile. The W3C calls this Art Direction.
  • The website provides different image formats for one image, e. g. WebP and PNG.

I would state as a rule of thumb:

Only use <picture> if you really need its features.

<picture>  
    <!-- 16:9 on "desktop" -->
    <source
        media="(min-width: 720px)"
        srcset="
            wide/small.png 480w,
            wide/medium.png 720w,
            wide/big.png 1020w
        "
        sizes="100vw"
    />
    <!-- square on "mobile" -->
    <source
        srcset="
            square/small.png 480w,
            square/medium.png 720w,
            square/big.png 1020w
        "
        sizes="100vw"
    />
    <img
        src="wide/medium.png"
        alt="responsive image"
    />
</picture>  

If you want to learn more about the different Responsive Image techniques, I would recommend reading the excellent article about Responsive Images on A List Apart.

Fitting Images Into Columns

Websites use different layouts. That’s why each implementation of Responsive Images should be adjusted to the underlying page layout.

My example layout is pretty straightforward:

<div id="wrapper">  
    <nav id="menu" role="navigation">
        Main menu
    </nav>      
    <main id="content" role="main">
        Content
    </main>
</div>  

On mobile devices (width < 740px) the content column gets a small padding:

#wrapper {}

#menu {}

#content {
    padding: 10px;
}

On tablet-sized devices (width ≥ 740px) the two columns are displayed side by side with a fixed-width wrapper:

@media only screen and (min-width: 740px) {

    #wrapper {
        width: 720px;
    }

    #menu {
        float: left;
        width: 200px;
    }

    #content {
        margin-left: 220px;
        padding: 20px;
    }

}

On desktop-sized devices (width ≥ 1020px) the content gets more space, but is also limited in its total width:

@media only screen and (min-width: 1020px) {

    #wrapper {
        width: 960px;
    }

    #menu {
        float: left;
        width: 300px;
    }

    #content {
        margin-left: 320px;
        padding: 20px;
    }

}

This is a pretty common Responsive Webdesign pattern that is also used by Twitter Bootstrap.

So how does this affect the Responsive Images? Well, the width of the content column isn’t always as wide as the viewport because of the second column next to it. This needs to be considered so that the browser doesn’t always load images that are as wide as the viewport.

Viewport Width Calculation Image Width
< 740px 100% − 2 × 10px < 720px
≥ 740px 720px − 220px − 2 × 20px 460px
≥ 1020px 960px − 320px − 2 × 20px 600px

Let’s put these values into code using the sizes attribute:

<img  
    …
    sizes="
        (min-width: 1020px) 600px,
        (min-width: 740px) 460px,
        calc(100vw - 20px)
    "
/>

If you are using the <picture> tag, you could also adjust the sizes attribute for each of your <source> tags depending on the associated media query. Hint: This is where things get complicated.

If this sounds easy so far, think about what would happen if the content image doesn’t use the full width of the column. Of course you can implement the perfect solution, but it will get more complicated the more cases you want to cover. For this example I stick to images that fill the column width – at least for now.

Size options

The next step is to decide which image sizes should be provided. There are some factors that could and probably should influence your decision:

  1. Column sizes (see above)
  2. Popular client devices and their
    • viewport width
    • pixel density
    • possibility of orientation change

Based on those factors you can create device groups and cover each group with an image resource in the appropriate size.

Image sizes based on column sizes

As a first step we take the two fixed column widths of our layout:

Image Width Purpose
460w on 1x devices between 1020px and 740px viewport width
600w on 1x devices above 1020px viewport width

Next we add high-dpi versions for high-resolution displays:

Image Width Purpose
920w on 2x devices between 1020px and 740px viewport width
1200w on 2x devices above 1020px viewport width

Image sizes based on client devices

For everything below 740px viewports we can rely on popular screen sizes to make an educated decision. To calculate the image width that will be used on a specific screen, we can use the following formulae:

  1. For viewports < 740px:

    (viewport width − padding) × pixel density

  2. For viewports ≥ 740px:

    fixed width × pixel density

Let’s get started with some smartphones:

Device Viewport Width Padding Pixel Density Calculated Image Width Image Width
iPhone 5 (portrait) 320px 20px 2x 600w 600w
iPhone 5 (landscape) 568px 20px 2x 1096w 1100w
iPhone 6 (portrait) 375px 20px 2x 710w 780w
iPhone 6 (landscape) 667px 20px 2x 1294w 1200w
iPhone 6+ (portrait) 414px 20px 3x 1182w 1200w
iPhone 6+ (landscape) 736px 20px 3x 2148w 1200w
Nexus 4 (portrait) 384px 20px 2x 728w 780w
Nexus 4 (landscape) 598px 20px 2x 1156w 1200w
Nexus 5 (portrait) 360px 20px 3x 1020w 1100w
Nexus 5 (landscape) 598px 20px 3x 1734w 1200w
Samsung Galaxy S5 (portrait) 360px 20px 3x 1020w 1100w
Samsung Galaxy S5 (landscape) 640px 20px 3x 1860w 1200w
Samsung Galaxy S4 mini (portrait) 360px 20px 1.5x 510w 600w
Samsung Galaxy S4 mini (landscape) 640px 20px 1.5x 930w 1100w
LG G3 (portrait) 360px 20px 4x 1360w 1200w
LG G3 (landscape) 640px 20px 4x 2480w 1200w

As you can see I already added two more image sizes in the last column based on the calculated widths, namely:

Image width Purpose
780w for small tablets or bigger phones
1100w for big phones

And as I already mentioned tablets, here are some popular tablets that I checked:

Device Viewport Width Padding Pixel Density Calculated Image Width Image Width
iPad (portrait) 768px 40px 1x 728w 460w *
iPad (landscape) 1024px 40px 1x 984w 600w *
iPad Retina (portrait) 768px 40px 2x 1456w 920w *
iPad Retina (landscape) 1024px 40px 2x 1968w 1200w *
Nexus 7 (portrait) 600px 20px 1.325x 768w 780w
Nexus 7 (landscape) 960px 40px 1.325x 1219w 780w *
Nexus 7 2013 (portrait) 600px 20px 2x 1160w 1200w
Nexus 7 2013 (landscape) 960px 40px 2x 1840w 920w *

* Image doesn't cover the whole viewport width because of the sizes attribute.

As a side note it is important to mention that this is not an exact science (as you probably can see). Devices come and go all the time. But from my point of view it is helpful to have at least some kind of orientation when we make those decisions.

Conclusion

Now that we have decided on the image sizes, we can generate the final image tag:

<img  
    src="normal.png"
    srcset="
        small.png 460w,
        normal.png 600w,
        medium.png 780w,
        large.png 920w,
        extralarge.png 1100w,
        huge.png 1200w
    "
    sizes="
        (min-width: 1020px) 600px,
        (min-width: 740px) 460px,
        calc(100vw - 20px)
    "
    alt="responsive image"
/>

And when I say generate, I mean generate. While some people (including me by the way) might have fun creating those cryptic HTML tags by hand, the more obvious solution is to let computers do the work.

In fact responsive images are both a client and a server problem. It is important for web designers to understand the concept behind it and to decide which aspect ratios and image sizes are appropriate for the layout at hand. Everything after that should be a server task.

Which is why I wrote this blogpost on a domain called somethingphp.com. My next article will be about the implementation of this example layout in TYPO3 CMS, which added (some kind of) support for responsive images in its latest LTS version (6.2). Stay tuned.

Update: Here it is. Responsive Images and TYPO3


  1. Twice as big in both height and width.