Grow your CSS skills. Land your dream job.

The Layered Look: Better Responsive Images Using Multiple Backgrounds

Published by Guest Author

The following is a guest post by Parker Bennett. Parker has written for CSS-Tricks before, in his article Crop Top dealing with the positioning of fluid images. This is a great follow up to that, presenting another option in the never-ending responsive images saga. It's also an interesting contrast to yesterday's post on responsive images - so you can see how very different the approaches to this problem can be.

We want fast page loads. We want retina images. Can we have it all?

Faking "lowsrc" — high-res color swaps in once mobile-first is loaded.

We don't want to serve giant-sized images to people who don't need them. There are several approaches for not doing this — including Scott Jehl's picturefill, server-side solutions, and lazy-loading techniques — but the simplest method may be to use a background-image and the magic of CSS media queries. This way, your lucky retina users get the high-res @2x versions, and the rest of us… well, at least I don't have to wait for mammoth files to download.

(To help illustrate below, I've made my "mobile first" default image black and white, the medium-sized version sepia, and there's a larger color version if you resize wide enough or are on a high-dpi screen.)

Small black & white default, swaps to color when wider. Before images are cached there's a small flash while they load.
/* base background-image class */
.bg-image {
  width: 100%;
  /* default background-position */
  background-position: center center;
  /* lt ie 8 */
  -ms-background-position-x: center;
  -ms-background-position-y: center;
  /* scale proportionately */
  background-size: cover;
  /* IE8 workaround - http://louisremi.github.io/background-size-polyfill/ */
  -ms-behavior: url(/backgroundsize.min.htc); }

/* mobile-first default (b&w) */
.bg-image-sedona {
  background-image: url(img/photo-sedona_512x320.jpg);
  background-position: center 21%; }

/* example media queries (IE8 needs this:
   http://code.google.com/p/css3-mediaqueries-js) */
@media
  /* "mama-bear" - plus any-retina */
  only screen and min-width : 513px,
  only screen and (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (        min-device-pixel-ratio: 1.5) {

    /* mid-size (sepia) */
    .bg-image-sedona {
      background-image: url(img/photo-sedona_1024x640.jpg); }
  }

@media
  /* "papa-bear" - plus larger retina */
  only screen and (min-width : 1025px),
  only screen and (min-device-width : 768px) and (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (min-device-width : 768px) and (        min-device-pixel-ratio: 1.5) {

    /* high-res (color) */
    .bg-image-sedona {
      background-image: url(img/photo-sedona_1024x640@2x.jpg); }
  }

The div displaying the background-image needs a height, which can be set manually, or, as I've done here, by wrapping a transparent "proxy" img set to scale responsively (more on this here).

Now, as you may have noticed, the first time a page renders a large image, there can be a noticeable delay as it loads. Even smaller images, before they're cached, may display an annoying flash as they load or get swapped in. But we can fix that…

CSS3 multiple-backgrounds: How They Stack Up

Edit on CodePen

Newer browsers let us stack background images by declaring multiple values separated by a comma. In this way, we can display the original cached image while the replacement image smoothly loads over it (note the stacking order in the code below).

Single background on top, multiple backgrounds on bottom.

To see this at work, narrow the browser window and empty the cache (choose "Clear Browsing Data" in the Chrome menu or "Empty Caches" in Safari's Develop menu). Now reload the page. Scroll back down here and widen the window until the color images load above. (Or try this pop-up window.)

Unfortunately, older browsers such as IE8* see multiple background declarations and throw up their hands — displaying nothing (yikes!). So we need to use modernizr.js to feature-detect, and create a fallback (if we want those browsers to show something larger than the mobile-first default):

/* .bg-image and .bg-image-sedona same as above.
   .multiplebgs class added by modernizer.js. */

@media
  /* "mama-bear" - plus any-retina */
  only screen and min-width : 513px,
  only screen and (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (        min-device-pixel-ratio: 1.5) {

    /* no-multiplebgs - mid-size fallback (sepia) */
    .no-multiplebgs .bg-image-sedona,
    /* upscale to mid-size if no javascript */
    .no-js .bg-image-sedona {
      background-image: url(img/photo-sedona_1024x640.jpg); }

    .multiplebgs .bg-image-sedona {
      background-image:
        /* mid-size on top (sepia) */
        url(img/photo-sedona_1024x640.jpg),
        /* mobile-first default on bottom (b&w) */
        url(img/photo-sedona_512x320.jpg);
      }
  }

@media
  /* "papa-bear" - all three images */
  only screen and (min-width : 1025px) {

    /* no-multiplebgs fallback is above */

    .multiplebgs .bg-image-sedona {
      background-image:
        /* high-res on top (color) */
        url(img/photo-sedona_1024x640@2x.jpg),
        /* mid-size in middle (sepia) */
        url(img/photo-sedona_1024x640.jpg),
        /* mobile-first default on bottom (b&w) */
        url(img/photo-sedona_512x320.jpg);
      }
  }

@media
  /* larger retina device - triggered immediately,
     so mid-size image not needed */

  only screen and (min-device-width : 768px) and
    (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (min-device-width : 768px) and
    (        min-device-pixel-ratio: 1.5) {

    /* no-multiplebgs fallback is above */

    .multiplebgs .bg-image-sedona {
      background-image:
        /* high-res on top (color) */
        url(img/photo-sedona_1024x640@2x.jpg),
        /* mobile-first default on bottom (b&w) */
        url(img/photo-sedona_512x640.jpg);
    }
  }

Standard vs. Progressive JPEGs

For JPEGs, the way an image renders over another image in a multiple background depends on how it's been saved. A standard JPEG "paints" the image sequentially as it's downloading. Progressive JPEGs "pop on" once completely downloaded. (The standard way seems smoother to me.) Note that image compressors like ImageOptim have their defaults set to save progressively (Jpegrescan is checked) because it saves a little space.

Of course, we don't want users to download images unnecessarily, or overcomplicate our upkeep, so it's important we keep our breakpoints restrained and think them through logically. But now that we can make image swapping less conspicuous, it opens up some possibilities…

Faking "lowsrc"

Edit on CodePen

Back in the days when steam powered the Internet, dial-up access was so slow they created a special attribute so that users would see something during the minute and a half it took to download their animated gifs: it was called "lowsrc" and it looked like this: IMG SRC="big.gif" LOWSRC="small.gif".

Browsers stopped supporting this back in the late '50s.

But something like this might be handy now, so that users can see something during the two-and-a-half seconds it takes to download their retina-ready high-res images. (And don't forget, 4K is coming.)

Modern browsers are pretty smart about filling in images as soon as they're fetched, so by specifying smaller, more compressed "lowsrc" images as the default, then including them stacked beneath the @2x retina images in our CSS media queries, things are likely to feel snappier. We can go one step further using jQuery…

The idea is to hold off image swapping until the page is rendered completely with our default "lowsrc" images. Then we use jQuery to add an "hd" class to our main "bg-image" class, which triggers our media queries to swap the images. We could also hold off and "lazy load" the higher-res images as we scroll to them, using something like the jQuery Waypoints plug-in.

/* .bg-image and .bg-image-sedona same as above
   .hd class added by jQuery after page loads
   (or perhaps "lazy loaded" as user scrolls) */

@media
  /* "mama-bear" - plus any-retina */
  only screen and (min-width : 513px),
  only screen and (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (        min-device-pixel-ratio: 1.5) {

    /* no-multiplebgs - mid-size fallback */
    .no-multiplebgs .bg-image-sedona.hd,
    .no-js .bg-image-sedona {
      /* mid-size (sepia) */
      background-image: url(img/photo-sedona_1024x640.jpg); }

    .multiplebgs .bg-image-sedona.hd {
      background-image:
        /* mid-size on top (sepia) */
        url(img/photo-sedona_1024x640.jpg),
        /* mobile-first "lowsrc" on bottom (b&w) */
        url(img/photo-sedona_512x320.jpg); }
  }

@media
  /* "papa-bear" - size only */
  only screen and (min-width : 1025px) {

    /* no-multiplebgs fallback is above */

    .multiplebgs .bg-image-sedona.hd {
      background-image:
        /* high-res on top (color) */
        url(img/photo-sedona_1024x640@2x.jpg),
        /* mid-size in middle (sepia) */
        url(img/photo-sedona_1024x640.jpg),
        /* mobile-first "lowsrc" on bottom (b&w) */
        url(img/photo-sedona_512x320.jpg); }
  }

@media
  /* larger retina device, triggered immediately,
     so mid-size image is not needed */
  only screen and (min-device-width : 768px) and
    (-webkit-min-device-pixel-ratio: 1.5),
  only screen and (min-device-width : 768px) and
    (        min-device-pixel-ratio: 1.5) {

    /* no-multiplebgs fallback is above */

    .multiplebgs .bg-image-sedona.hd {
      background-image:
        /* high-res on top (color) */
        url(img/photo-sedona_1024x640@2x.jpg),
        /* mobile-first "lowsrc" on bottom (b&w) */
        url(img/photo-sedona_512x320.jpg); }
  }
/* waits until everything is loaded, not just DOM is ready */
$(window).load(function() {

  $('.bg-image').addClass('hd');

});

See this "Faking lowsrc" demo in action

See an example with "lazy loading" at work.

/* "lazy loads" when .bg-image appears in viewport -
   http://imakewebthings.com/jquery-waypoints/ */

$('.bg-image').waypoint(function(direction) {
  if (direction === 'down') {
    $(this).addClass('hd');
  }
}, { offset: 'bottom-in-view', triggerOnce: true });

/* other offsets: '100%' (image top at viewport bottom),
   '125%' (just beyond the viewport, about to scroll in) */

Wrapping Up

Ideally, I'd like to see this work in a more automated way, like picturefill.js, but extrapolating from a mobile-first img rather than a data-src attribute. What do you think? You can take a look at the source code for more, see all the demos on CodePen, or download the example files here. If you have any questions, comments, or corrections, drop me a line: parker@parkerbennett.com.


* IE8 doesn't support multiple backgrounds, but if you can declare a width and height for your image, you could work something similar using this pseudo-element approach by Nicolas Gallagher.

Comments

  1. On a recent project I used a small image for mobiles, instead of the transparent “proxy” img. Then on larger screens I hide the image and display a larger background image instead. Whilst this is straight-forward it doesn’t account for additional images for things like retina displays.

    I find it incredibly frustrating that there isn’t a “perfect” method of doing this. Every method sucks in one way or another, I guess it’s a case of finding the method which sucks the least.

    • Correct me if I’m wrong but can’t you use media queries to hide and display for low res, high res and high dpi?

    • I think there is a solution but the strange thing is that it works on Blackberry but on the iPhone it spits it straight out! … I am not sure what browser you are using although I am using Opera mobile but on my iPhone I use Operate mobile as well but some strange reason it messes everything up!

      Gareth if your on iPhone is this the 4, 4S or 5? Thanks

  2. This is an interesting technique, but it does mean that you load multiple resources. Fine if you are connected to a broadband connection on your retina MBP, however if you are on your retina iPhone or other high-res mobile device whilst you are not at home, you are increasing the data you are downloading.

    This is something that should really be avoided. Who wants to waste data? Someone somewhere has the perfect solution, but is holding out on us.

    • That’s what so crazy about this whole situation. Responsive images is a balancing act between:

      • Semantics / Accessibility
      • Syntax (old? new? new with fallback?)
      • Requests (less is always better, but is it the only option?)
      • How to decide what to serve (media queries? bandwidth? something new?)
      • Art direction (do we need it?)
      • JavaScript (does it need to be involved or not?)
      • CSS (does it need to be involved or not?)
      • Browsers (is the responsibility solely on them?)
      • Servers / Protocols (can they help?)
      • Standardization

      That’s what makes this such a wildfire.

    • Mark
      Permalink to comment#

      If a min-width and max-width value was set, there would be only one file loaded right?

    • Parker
      Permalink to comment#

      Sorry if I wasn’t more clear, but I think it’s possible to use media queries to cut down on unnecessary downloading. For example, we can target a retina iPhone separately using min-device-width and min-device-pixel-ratio. So they get the mid-size image, but not the giant one.

      There’s no perfect solution, only considered compromises. Here’s my usual breakdown:

      Vector when possible: icon font or SVG.

      UI elements are sprited: both 1x and 2x versions (media query for retina).

      For smaller images, displayed at 512px wide or less, I use a single fluid img where the actual file width is 1.5x — so no more than 768px (src height and width declared at the displayed max-width or proportionately larger). This is a compromise that looks pretty good on retina and is easy to maintain.

      It’s the bigger images where I start considering background-images and media queries: the big background-image on the home page, where it looks goofy to see the type render and then wait for the image to load underneath. The photographer’s or architect’s website that they want to show off on a retina iPad. Even so, I tend to keep things at 1.5x rather than @2x, no image wider than 2048px.

      For these bigger images, I think it’s preferable to see something quickly, allowing users to scan the page rather than see “loading” graphics, hence the “lowsrc” idea. Once I’ve committed to displaying this mobile-first image (25K or so), it’s no more bandwidth to use multiple backgrounds to make the transition smoother — to keep displaying the image that’s already cached while we fetch the bigger image and load it over it. It’s also no more of a bandwidth hit to use this “lowsrc” image as my div-height-creating “proxy”.

      As Dave Rupert points out, 92.8% of all “mobile” screens are @1.5x or higher, so until we can easily account for bandwidth we have to make decisions about what delivers the best experience, and how much work we want to do to get there. I haven’t tried yet, but I would think a SASS mixin could be helpful here.

  3. Alexander
    Permalink to comment#

    thx

  4. The waste of data is one point but the waste of time is another. The load of multiple resources takes more time and cause slower response and it ruines user experiences.

  5. I agree with Alex McCabe about increasing both data and requests.
    While this would look nice on a desktop (and it does by the look of the examples) it would pretty much suck on a data plan and a mobile device.

    Using a “loading” image is an option though – the user expects something over there. This image could be set as a scaled background image of every container that’s loading an image.

    I’d choose the server-side options for doing this.

    Great article though, thanks.

  6. name
    Permalink to comment#

    i don’t think that LOWSRC was available in the 50’s. Back in the day the internet wasn’t invented yet.

  7. I think this is great—definitely a step in an interesting direction.

  8. Man, responsive images is such a beast. I usually just follow the 1.5x rule (where 1.5x the size is much smaller than 2x file-size-wise, but much closer to 2x looks-wise on Retina (since Retina is more pixels than the human can differentiate between at the average viewing distance)).

    Obviously not an option for photos, but I have been using SVGs any chance I get (i.e. the logo on my new biz site). The files were already tiny and it was perfectly sharp even at full screen on Retina. But then I realized the SVG had hidden layers from AI and I cut the size down to 1/3 of that! I exported PNG fallbacks and they are 4x as big (and I didn’t even export very big versions).

    I am surprised at SVG support. Basically IE8 is all you really gotta look out for.

    • And don’t forget gzip compression on your server. Even smaller file sizes! I’ve been using SVG images every chance I get too.

  9. Permalink to comment#

    @ Chris: One ALWAYS needs Art Direction :)

    On a technical note am conflicted about this technique. I am not comfortable with dupping the # for request plus extra data transfer for this method. And remember not even 3G is every where yet.. sometime am outside Madison.. goin #@^&&$%%$%$#@^&*$^&#!!!

    I once pondered about the necessity for low res thumbs in a gallery, thinking that the user would end up seeing all the images anyway so why add the thumbnails to the weight of the page… The thing is not every user ( in fact few) would click through an entire gallery in one sitting. In other words thumbs served the purpose of quick navigation, and possibly selective loading via links/.js. This is a converse situation, if we are talking bg images and figures( text related images), avatars, etc. Every user DOES ‘sit through’ the bg of a website ,figures and avatars. So while it does cover up the ‘slow connection effects’ it seems an expensive way of doing it for ‘non optional’ content

  10. I’d have to agree that this seems more like a time eater rather than something that creates a significantly better experience. I’d like to think I’m a little more patient than your average user so I’ll wait for an image to load if I have an even small interest in it. But then if it does take too long, I’ll bounce. But it is something to be worked with for sure. Seeing mixed review here though! Thanks for the read.

  11. Julian Gerke
    Permalink to comment#

    I dont see the point of doing this layer thingy in a responsive website. You can just load the size you need with media queries. The flickering only appears if you change your browser size. A normal user is not doing this while browsing your content. But for retina support its maybe a solution to load an image beforehand. Yea and the extra data transfer is a drawback offcourse…

  12. Kyle Breckenridge
    Permalink to comment#

    I was just working on a responsive site and used Scott Jehl’s polyfill with a fallback to the mobile sized photos and I really liked it. Using wordpress to handle all the image re-sizes and just writing a quick function to handle the html output it was super easy to use.

    One thing I didn’t even think of until later, and something I think this solution will suffer from as well, is that, is image searching. Since the only thing mentioned in markup is the mobile fallback, doesn’t that mean that this is the only image google will find when it crawls the page? So, if I’m a news website writing an article about something and have a beautiful high-rez image that only exists as a background in the css files, will a user searching through high rez images on google ever be able to find it?

    Super small use case I know, but it came up after my last project launched and I still haven’t figured out an answer.

  13. When ever I think about responsive design IE8 always in my mind it can waste of time while working with difference source of data I always work with the simplest and fastest way so that I can upgrade that website later too if required.

  14. Permalink to comment#

    At the moment images in responsive contexts just seem like an ultra pain in the ass. Yet to see a “solution” that will actually work or be sustainable in the longer term.

    I think at the moment I’ll just stick to single images that are as acceptable as they can be.

    1.5x or 2x images are great but how many people are using a retina screen? Likely to be a small amount of the overall whole.

  15. Alex Bell
    Permalink to comment#

    This is a fun tour of css tech, but really a bad idea from the point of view of performance. Not just because of duplicate requests, but because no image request will be fired until the stylesheet is fully parsed and some of layout has occurred. In modern browsers, with lookahead pre-parsers, background images load way later than imgs in markup. Try any complex (30k+) stylesheet on any complex (80k+) page on a slow connection and you will feel the pain watching the layout “animate” into view. These are not the droids we’re looking for. (For servers running PHP, Adaptive Images is still for my $ pretty much the best thing going.)

    • Parker
      Permalink to comment#

      http://adaptive-images.com/

      From their website: “AI checks the User Agent String: if it finds ‘mobile’, it will send the lowest resolution defined in $resolutions, otherwise it assumes you’re on a large device and delivers the highest.”

      This is what Dave Rupert was getting at: if 93% of mobile devices are high dpi, does the “mobile” designation correlate to 1x images anymore? Can we adapt Adaptive Images to serve a mid-size, higher-res image to retina mobile screens?

  16. Luís Almeida
    Permalink to comment#

    Great article and technique but it seems a bit over-engineered to me.

    IMHO Unveil.js is a much much simpler solution to lazy-load and conditionally serve high-resolution images to devices with retina displays:

    <img src="loader.gif" data-src="img.jpg" data-src-retina="img-retina.jpg" />

    $(function() { $("img").unveil(); });

    I wrote the script so it may seem a bit pretentious, but I’m dropping the link here as I truly believe it’s a very good and simple solution to this problem:
    luis-almeida.github.io/unveil

    • Parker
      Permalink to comment#

      This is a great solution in many cases, Luís—nice work! (On your github page, I noticed that your noscript example declares a src twice: img src="bg.png" src="img1.jpg. If only that worked, I wouldn’t have jumped through so many hoops to fake “lowsrc”!)

      I put together a CodePen using unveil.js but displaying a “lowsrc” img instead of a “loading.gif.” It’s definitely simpler, if you don’t otherwise need to use a background-image (say, for cropping flexibility, or for, you know, a background).

      One of my concerns, though, is triggering solely based on screen density. If screensiz.es is right, 93% of all mobile devices have a px density of 150% or higher, but most don’t need a desktop-sized retina image for their smaller screen — they could get decent results with 768-864px. Using unveil.js, there’s no middle-ground.

  17. You can target any mobile device using conditional tags

  18. Parker
    Permalink to comment#

    http://wpsites.net/web-design/conditional-tags-mobile-devices/

    Thanks to Brad for highlighting this capability in WordPress. Is there a code snippet or example of how this might work? For me, the ideal would be to serve a mid-size higher-res img src to mobile retina screens, and the highest-res img src to other retina screens.

  19. Ferdy

    Or, one could throw away all these image switching techniques and go for one image to rule them all:

    http://blog.netvlies.nl/design-interactie/retina-revolution/

    It won’t work for PNG and it does not cover art direction, but other than that, I find it works beautifully. I’m currently redesigning a photo community site (go figure) where the new version will use this technique. Images are smaller in filesize yet have 4 times the pixels. It really works. I think it is a technique that should be considered before anything else, since it avoids a lot of complexity. Furthermore, I think any solution requiring multiple images is problematic, given that most of the web is powered by a CMS that may not support it.

    • Brad Dalton

      WordPress already generate 4 different sized images according to your media settings.

      Isn’t difficult to add more custom sizes and then use them with specific mobile conditional tags.

  20. Peter

    Seems like a cool idea but i don’t like the idea that we define half of the images in the html and the rest in css…

  21. Ramesh Chowdarapally

    Really Interesting. Have to work on it.

  22. Here an alternative solution for adaptive images: http://litesite.org/holygrail/stage2/

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".