Responsive background images with srcset and sizes

A while ago I searched around for a quick and easy solution to provide responsive background images. Somewhere I stumbled upon a fiddle demonstrating an amazingly neat trick. I tried this approach in a few projects and started some cross browser testing. It worked like a charm and I'd like to share and explain this idea.

srcset and sizes FTW!

The srcset and sizes attributes, which the HTML5 standard specifies for the img tag, are around for a while now. They made their way into most oft the major browsers and Can I Use states a global support of around 85%.

Unsupported browsers can be extended easily with the polyfill picturefill - mainly IE 9/10/11, macOS Safari 8, iOS 8 Safari and the Android 4 built-in browser. Therefore you can fairly say it's a good choice to count on this standard when searching a solution for responsive images.

Current state of responsive background images.

The whole HTML5 specification linked above handles the img tag. Background images set with CSS are a totally different story. In the CSS world the corresponding standard is called image-set. A brief look on the global support shown on Can I Use indicates nearly 80%. This sounds pretty good in the first place. Is it?

Unfortunately there are some hitches with image-set. First of all it's an absolute draft, an unofficial standard. Thus Can I Use says the unprefixed support is 0%.

Within the CSS specification you can find some issues emphasized, e.g. incompatibilities with the HTML5 specification. This means the spec will change in the future, which forces you to change your implementation. Further it's assumable that the browser support will increase quite slowly, because browser vendors have to change their implementation aswell.

Speaking of browser support it's worth noting, that there is no support at all for Edge and Firefox. On Github you will find a polyfill called image-set-polyfill. Judging by some key metrics like stars, contributors, forks or commits you can say that image-set-polyfill is not very popular and wide spread compared to picturefill. It may be kind of a risk to rely on such software in production.

In conclusion I'd not recommend using image-set for responsive background images.

One trick to rule them all.

If you are fine with a solution depending on JavaScript in all browsers, there is a stunning simple and obvious trick for responsive background images.

You can use the full power of the img tag with srcset and sizes attributes to load the perfect image for current device or display - but not to display it on the page. The img tag is hidden by display: none;. Don't worry, the image file will be loaded anyway by the browser. You can hook up the image's load event with JavaScript, grab the source and set it as background-image for another DOM element. The load event will also fire when the browser window gets scaled and the image source changes. Awesome. Mind blown.

Here is a sample markup to explain this approach:

<div responsive-background-image>  
  <img src="/images/test-1600.jpg"
    sizes="
      (min-width: 768px) 50vw,
      (min-width: 1024px) 66vw,
      100vw"
    srcset="
      /images/test-400.jpg 400w,
      /images/test-800.jpg 800w,
      /images/test-1200.jpg 1200w" >
</div>  

As you can see in the corresponding CSS, the image will be set as background on the div. The img tag itself will be hidden:

[data-responsive-background-image] {
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
  display: inline-block;
  width: 24%;
  padding-bottom: 56.25% /* 16:9 ratio */
}

[data-responsive-background-image] img {
  display: none;
}

This is the JavaScript class where all the magic happens:

class ResponsiveBackgroundImage {

    constructor(element) {
        this.element = element;
        this.img = element.querySelector('img');
        this.src = '';

        this.img.addEventListener('load', () => {
            this.update();
        });

        if (this.img.complete) {
            this.update();
        }
    }

    update() {
        let src = typeof this.img.currentSrc !== 'undefined' ? this.img.currentSrc : this.img.src;
        if (this.src !== src) {
            this.src = src;
            this.element.style.backgroundImage = 'url("' + this.src + '")';

        }
    }
}

The constructor() searches for an inner img node and subscribes to the load event. Once triggered it grabs the src from this.img.currentSrc with a fallback for all unsupported browsers (without polyfill). If the src changed it will be set as CSS background-image.

You can use this class for all of your responsive background images like following.

let elements = document.querySelectorAll('[data-responsive-background-image]');  
for (let i=0; i<elements.length; i++) {  
  new ResponsiveBackgroundImage(elements[i]);
}

This approach is amazingly easy, efficient and convenient. This is definitely a nifty trick I'm going to use in upcoming projects.