Why we can’t do real responsive images with CSS or JavaScript

I’m writing a talk on <picture>, srcset and friends for Awwwards Conference in Barcelona next month (yes, I know this is unparalleled early preparation; I’m heading for the sunshine for 2 weeks soon). I decided that, before I get on to the main subject, I should address the question “why all this complex new markup? Why not just use CSS or JavaScript?” because it’s invariably asked.

But you might not be able to see me in Catalonia to find out, because tickets are nearly sold out. So here’s the answer.

All browsers have what’s called a preloader. As the browser is munching through the HTML – before it’s even started to construct a DOM – the preloader sees “<img>” and rushes off to fetch the resource before it’s even thought about speculating about considering doing anything about the CSS or JavaScript.

It does this to get images as fast as it can – after all, they can often be pretty big and are one of the things that boosts the perceived performance of a page dramatically. Steve Souders, head honcho of Velocity Conference, bloke who knows loads about site speed, and renowned poet called the preloader “the single biggest performance improvement browsers have ever made” in his sonnet “Shall I compare thee to a summer’s preloader, bae?”

So, by the time the browser gets around to dealing with CSS or script, it may very well have already grabbed an image – or at least downloaded a fair bit. If you try

<img id=thingy src=picture.png alt="a mankini">
@media all and (max-width:600px) {
 #thingy {content: url(medium-res.png);}

@media all and (max-width:320px) {
 #thingy {content: url(low-res.png);}

you’ll find the correct image is selected by the media query (assuming your browser supports content on simple selectors without :before or :after pseudo-elements) but you’ll find that the preloader has downloaded the resource pointed to by the <img src> and then the one that the CSS replaces it with is downloaded, too. So you get a double download which is not what you want at all.

Alternatively, you could have an <img> with no src attribute, and then add it in with JavaScript – but then you’re fetching the resource until much later, delaying the loading of the page. Because your browser won’t know the width and height of the image that the JS will select, it can’t leave room for it when laying out the page so you may find that your page gets reflowed and, if the user was reading some textual content, she might find the stuff she’s reading scrolls off the page.

So the only way to beat the preloader is to put all the potential image sources in the HTML and give the browser all the information it needs to make the selection there, too. That’s what the w and x descriptors in srcset are for, and the sizes attribute.

Of course, I’ll explain it with far more panache and mohawk in Barcelona. So why not come along? Go on, you know you want to and I really want to see you again. Because I love you.

9 Responses to “ Why we can’t do real responsive images with CSS or JavaScript ”

Comment by Le Dahu Lévogyre


If < font > is faster than css styling (I’m pretty sure it was), would you recommend to use it too ?

Do we always need to write ugly code to help circumvent technical difficulties of browsers, and to gain a few milliseconds ?

I mean, is it worth it ? What are the real life numbers ?

I wish I’d be in Barcelona. I’m sure your panache can convince me of anything (but only when you wear your crocheted mankini)

Comment by Ahmad Alfy

wow, I didn’t know we could use content attribute on an image tag to inject an image 🙂

My take on this simple technique is that you will have to CSS for every blog post you publish with an image … There is a conflict here I believe, CSS now is being used to inject content.

Comment by Anselm Hannemann

Le Dahu Lévogyre,

neither CSS nor JavaScript isn’t thought to serve real content. Both techniques are thought to enhance the layout and functionality of HTML. Seeing the whole topic within that regard and taking art direction into account it suddenly makes no sense to misuse CSS or JS for swapping out images.

I also wonder why any of the CSS or JavaScript solutions should be easier than adding a `srcset` attribute or using the `picture` element. And of course you usually let the CMS do the work of writing the tags out for you.

Ahmad, it’s not that any page uses JavaScript or CSS anyway. It’s the way how browsers work. Of course nearly every website uses all three techniques but that the important point is that CSS is loaded blockingly (nothing is being painted before CSS hasn’t been finished loading). Therefore you also shouldn’t put inline images into CSS or load webfonts through your main external stylesheet if possible (if it can’t be loaded your styles will be missing entirely).
The preparser instead starts as soon as the markup has been fetched and downloads the available images. It’s smart enough to recognize which of the responsive images to load before the CSS parsing has actually started.

It’s a common assumption that browsers work in a sequence. That’s not true: Modern browsers have at the very least 2-3 parallel workers, one for fetching resources, one for CSS and one for JavaScript. This asyncronous behavior makes displaying a website very fast.
If you now start to circumvent one of the threads and put the load of one thread on top of another thread this will of course slow down.

Hope this helps a little bit to understand the issues a little bit better.
– Anselm

Comment by Spudley

I guess you could defeat the preloader issue by having a minimal data URI in the img src attribute?

The the CSS could do what it needed to do to work out which image to load without worrying about whether one had already been pulled in by the pre-loader, and no double-download.

Sure, that little data URI would be ugly, and you lose the early download advantage of the preloader, but it surely is a solution to at least some of the points you raised?

Comment by Bruce

@Le Dahu Lévogyre – no idea if font elements are faster than CSS styling. But even if it, the difference would be trivial. In the case of double-downloading images – or downloading “retina” images far bigger than a feature phone needs, you’re talking significant differences. Eric Portis has numbers.

@ahmad alfy – quite; that’s why I don’t recommend the technique.

@spudley you could. But then you’re still waiting until later to download the images, as well as the maintenance hell of adding a CSS rule for every image on a site.

Comment by Stomme poes

This preloading is also why the only inline Javascript we have on our pages is listening for onError. It’s fugly and gross but otherwise not uncommon for the error event to fire long before your scripts ever load.

But I see art direction and how pretty the pictures are for well-off Retina-owners is the important part so we gets special new tags for that.

Would be nicer if we could say “for images with some funky new attribute, do not preload” so we could not only do the artsy stuff or the bandwidth stuff but also catch errors. Or whatever else people think of, like running custom image filters on the server (or even the client) before an actual image request is made…

Comment by Tim

Of course, if I were to use the inline CSS solution, I wouldn’t do it in the fashion of your example. The proper src image would be the mobile version, and then the media queries would have ascending calls (with both min and max width). Of course, that would leave pre-media query browsers with small inline images, but in most cases I don’t think that’s a dealbreaker. Still, not an ideal solution.

I’ve started using the server side solution offered by Matt Wilcox at adaptive-images.com. It too has some limitations, but would be interested in your take.

Comment by Bruce

@Carl – I’m not sure I understand what you mean by “is this an epiphany?”. We started working on responsive images years ago!

Leave a Reply

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> . To display code, manually escape it.