132

img with srcset attribute looks like a great way of doing responsive images. Is there an equivalent syntax that works in css background-image property?

HTML

<img src="small.jpg" srcset="medium.jpg 1000w, large.jpg 2000w" alt="yah">

CSS

.mycontainer {
    background: url('what goes here?');
}
kjones
  • 1,339
  • 1
  • 13
  • 28
Martin Algesten
  • 13,052
  • 4
  • 54
  • 77

10 Answers10

104

image-set is the equivalent CSS feature. We should add equivalent srcset functionality (defining resources according to their dimensions) to the spec.

Currently implemented in all major Browsers (Firefox, Chrome, Safari, Edge) with the -webkit- prefix. Safari only supports supports the x descriptors.

Yoav Weiss
  • 2,220
  • 4
  • 17
  • 22
  • 1
    is there any graceful fallback known or a polyfill supporting this? – Rachel Cantor Jun 06 '16 at 22:47
  • 5
    @RachelCantor Would a fallback be necessary? Only modern devices would have retina screens anyway, I'd have thought? – James Kemp Jul 21 '16 at 10:08
  • 8
    Only supporting the `x` descriptors seems pointless, i mean a background image could be full width on a 2x 'retina' desktop (5120w), which on mobile at 2x (720w) a ridiculous sized image for any mobile device to even consider as a web page background. Even using max-width media queries to serve a few different sizes is not that helpfull, because its not max-width of the background container, its max-width of the screen. – Chris Seufert Nov 16 '16 at 23:29
  • 2
    so if a browser doesnt support image-set what is the fallback? – O.MeeKoh Feb 09 '20 at 19:16
  • 1
    @O.MeeKoh According to [documentation](https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set), we have to specify the `background-image: url("");` before the line with the `image-set()` call. – quinqui Nov 18 '22 at 14:35
34

Another approach, which is quite frankly more robust, would be to adapt the characteristics and options of background images to an image with the srcset attribute.

To do this, set the image to be width: 100%; height: 100%; and object-fit: cover or contain.

Here is an example:

.pseudo-background-img-container {
  position: relative;
  width:400px;
  height: 200px;
}
.pseudo-background-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="pseudo-background-img-container">

<img class="pseudo-background-img" src="https://cdn3.tnwcdn.com/wp-content/blogs.dir/1/files/2016/12/Keep.jpg" srcset="https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2016/12/Keep.jpg 640w, https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2016/12/Keep-280x175.jpg 280w, https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2016/12/Keep-432x270.jpg 432w, https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2016/12/Keep-216x135.jpg 216w" sizes="(max-width: 640px) 100vw, 640px">

</div>

This may not be the best approach for everyone but I imagine it will get most the desired results without any javascript workaround.

Lorenz Meyer
  • 19,166
  • 22
  • 75
  • 121
Parks
  • 620
  • 6
  • 8
  • 1
    src-set does not take into account the image width, only the container width for object-fit: cover. This means that if your img tag ends up say 50px wide but 1000px high, the image that satisfies the 50px width is loaded and stretched to fit, maybe scaled from 240w to 1500px width, to display only a 50px slice. – Chris Seufert Feb 13 '18 at 04:45
  • 1
    I didn't know that `object-fit: cover;` exist, thank you. I for the other hand, it was the final touch to make my code work (I was using a `` tag) – Gendrith Feb 04 '21 at 03:27
  • Not answering the question in the way OP wants IMO, but a totally viable solution indeed. I would have recommended the same initially. – kissu Jan 19 '23 at 14:56
27

Pretty sure that:

background: -webkit-image-set( url('path/to/image') 1x, url('path/to/high-res-image') 2x );

works the same way. The browser will examine the images, see which fits best and will use that one.

SRing
  • 694
  • 6
  • 16
  • 2
    In 2018 this is still not supported by all browsers. https://caniuse.com/#search=image-set – BadHorsie Nov 22 '18 at 10:49
  • 3
    In 2021 it's still not fully supported... – Rick Kukiela Oct 22 '21 at 00:30
  • 3
    @HashimAziz - Yes if you're only using density declarations and you're ok with prefixes (which if you're running an auto-prefixer no big deal), then sure. My issue is that the "density" option is no where near as good as using the ###w declarations with `sizes` attributes which is really much better for defining when and where the browser should use which image. – Rick Kukiela Dec 17 '21 at 22:44
12

You can use media queries for your purpose. It's easy as this:

.mycontainer {    
    background-image:url("img/image-big.jpg"); // big image   
}

@media(max-width: 768px){
   .mycontainer {
        background-image:url("img/image-sm.jpg"); // small image  
    }
}

And I think it works on every browser who support media queries ;)

gtamborero
  • 2,898
  • 27
  • 28
  • 2
    If your image is set to cover a container, this does not take into account the image being stretched to fit the height, the box may be 2 or 3 times the height of the native image width and will, therefore look very poor in quality. – Chris Seufert Feb 13 '18 at 04:49
  • Media queries are not the same thing though. – Rick Kukiela Oct 22 '21 at 00:35
  • 1
    To further elaborate on my last comment. If you could just do all of this with media queries then there would be no reason to even have srcset or picture elements. Sure you can swap a background image out, but srcset and picture allow you to specify various image formats and provide container width context based on the media query (eg. >1024 its a side bar that is 140rem, below that, its full width) and that can be combined with 1x/2x densities to let the browser decide the best image for the context. None of that is possible with only media queries. – Rick Kukiela May 06 '22 at 23:42
11

For a polyfill, you can use an img with srcset as a mechanism for downloading the correct image size, then use JS to hide it and set the background-image of a parent element.

Here's a fiddle: http://jsbin.com/garetikubu/edit?html,output

The use of onload and putting the JS as a blocking script in the <head> is important. If you put the script later (say at the end of <body>), you can get a race condition where img.currentSrc hasn't been set yet by the browser. It's best to wait for it to be loaded.

The example allows you to see the original img being downloaded. You can easily hide it with some CSS.

Weston
  • 1,882
  • 1
  • 20
  • 26
  • Interesting. I would have expected the image to load twice when doing this. However, it does not appear to. – Perry May 30 '19 at 19:37
4

Similar solution using <picture> element: Tutorial here

Tutorial's case:

I’m doubtful that if I use the same image for a smaller screen size, the primary subject of my image may become too small in size. I want to display a different image (more focused on the primary subject) in a different screen size, but I still want to display separate assets of the same image based on device-pixel ratio, and I want to customize height and width of the image based on viewport.

Example code:

<picture>

<source media="(max-width: 20em)" srcset="images/small/space-needle.jpg 1x,
images/small/space-needle-2x.jpg 2x, images/small/space-needle-hd.jpg 3x">

<source media="(max-width: 40em)" srcset="images/medium/space-needle.jpg 1x,
images/medium/space-needle-2x.jpg 2x, images/medium/space-needle-hd.jpg 3x">

<img src="space-needle.jpg" alt="Space Needle">

</picture>
  • Position '' element as 'absolute' or 'fixed'. Set correct z-index+position: relative for its container if needed – Piotr Ścigała Mar 05 '17 at 17:56
  • gdaniel: Ready JS solution: https://github.com/code-mattclaffey/picture-element-background-attachment I've just tested it and it's working great! – Piotr Ścigała Mar 12 '17 at 18:14
  • JS solution, loading image via element and displaying it as a background-image: https://github.com/code-mattclaffey/picture-element-background-attachment – Piotr Ścigała Oct 03 '17 at 10:29
4

From 2021 onwards image-set() should be used for this purpose, according to CSSTricks.

Here's the full snippet that it recommends using for support of all modern browser versions:

.hero {
  /* Fallback */
  background-image: url("platypus.png");

  /* Chrome/Edge/Opera/Samsung, Safari will fallback to this as well */
  background-image: -webkit-image-set(url("platypus.png") 1x, url("platypus-2x.png") 2x);

  /* Standard use */
  background-image: image-set(url("platypus.png") 1x, url("platypus-2x.png") 2x);
}
Hashim Aziz
  • 4,074
  • 5
  • 38
  • 68
3

If you are using Foundation framework (https://foundation.zurb.com/), you can use Interchange plugin for that:

<div data-interchange="[assets/img/interchange/small.jpg, small], 
                       [assets/img/interchange/medium.jpg, medium], 
                       [assets/img/interchange/large.jpg, large]">
</div>

https://foundation.zurb.com/sites/docs/interchange.html#use-with-background-images

  • Thanks for this Martti. I've got this wired up nicely now alongside webpack's `responsive-loader` for a pretty sweet solution. – fredrivett Mar 28 '18 at 09:53
1

Based on @Weston's answer, I've built a more general jQuery solution, you can basically just copy&paste the JS and CSS and focus on the HTML part ;)

TL;DR - fiddle: https://jsfiddle.net/dpaa7oz6/

CSS

...to ensure images will be hardly visible while loading

.srcSet{
  position: fixed;
  z-index: 0;
  z-index: -1;
  z-index: -100;
  /* you could probably also add visibility: hidden; */
}

JS / jQuery

This script will go through all images that have srcSet class and bind load event that takes currentSrc (or src if not supported) and puts it as a background-image CSS to the closest parent with bgFromSrcSet class.

That itself would not be enough! So it also puts an interval checker on window load event to test if the load events have been completed, if not, it triggers them. (img load event is very often trigger only on first-time load, on following loads, image source could be retrieved from cache, resulting in img load event NOT being fired!)

jQuery(function($){ //ON DOCUMENT READY
  var $window = $(window); //prepare window as jQuery object

  var $srcSets = $('.srcSet'); //get all images with srcSet clas
  $srcSets.each(function(){ //for each .srcSet do...
    var $currImg = $(this); //prepare current srcSet as jQuery object

    $currImg
      .load(function(){ //bind the load event
          var img = $currImg.get(0); //retrieve DOM element from $currImg

          //test currentSrc support, if not supported, use the old src
          var src = img.currentSrc ? img.currentSrc : img.src;

          //To the closest parent with bgFromSrcSet class,
          //set the final src as a background-image CSS
          $currImg.closest('.bgFromSrcSet').css('background-image', "url('"+src+"')");

          //remove processed image from the jQuery set
          //(to update $srcSets.length that is checked in the loadChecker)
          $srcSets = $srcSets.not($currImg);

          $currImg.remove(); //remove the <img ...> itself 
      })
    ;      

  });

  //window's load event is called even on following loads...
  $window.load(function(){    
    //prepare the checker
    var loadChecker = setInterval(function(){
        if( $srcSets.length > 0 ) //if there is still work to do...
            $srcSets.load(); //...do it!
        else
            clearInterval(loadChecker); //if there is nothing to do - stop the checker
    }, 150);  
  });

});

HTML

...could look like this:

<div class="bgFromSrcSet">
  <img class="srcSet"
       alt=""
       src="http://example.com/something.jpeg" 
       srcset="http://example.com/something.jpeg 5760w, http://example.com/something-300x200.jpeg 300w, http://example.com/something-768x512.jpeg 768w, http://example.com/something-1024x683.jpeg 1024w, http://example.com/something-1000x667.jpeg 1000w"
       sizes="(max-width: 2000px) 100vw, 2000px"
  >
  Something else...
</div>

Note: class bgFromSrcSet must not be set to the img itself! It can only be set to the elements in the img DOM parent tree.

jave.web
  • 13,880
  • 12
  • 91
  • 125
1

I have used the Lazysizes plugin called bg-set for adding responsiveness to background images. It works similarly as srcset does and provides a set of multiple background images based on the device width.

<div class="lazyload" data-bgset="image-200.jpg 200w, image-300.jpg 300w, image-400.jpg 400w" data-sizes="auto">

The images in the data-bgset are the same, we are just providing the width specified image which the browser will be able to choose from.