Responsive design is everywhere; everyone’s at it because, well, it’s a great idea. It isn’t without its problems, however…
One of the more persistent issues is dealing with images. Resizing an image down to fit a smaller screen does work, but it’s a massive performance hit if your user is having to download a 1000px image to display on her 480px screen. Even if they’re on a super-fast WiFi connection, it makes no sense downloading 1000s of pixels if you can only display a fraction of that.
There have been several solutions posted and proposed around the internet but I’ve come up with a pretty humble one that you could use right away.
It’s a little fiddly, you have to hard-code some stuff and also process two images, but it does work and it is simple.
The premise is this; your <img /> element is the smaller of the two images, the image you want ‘mobile’ users to download. You also have a containing <div> to which you apply the large version of the image as a background through CSS.
You then hide the <img /> from ‘desktop’ users and show them the large, CSS background, and you hide the background image from ‘mobile’ users and just serve them the small inline image.
The benefits here are that you’re still using semantically sound markup; your HTML makes sense because there is an image element in there. The size of this image is irrelevant where semantics are concerned—a machine/browser etc doesn’t need to ‘see’ the image, it just needs to access its data. Further, screen readers can still access this image and its alt text, making this method nice and accessible.
So basically you are always serving an <img /> which is semantically sound, but you alter the cosmetics of that image depending on the size of device the user is using.
Here is some example code:
<!-- HTML -->
<div class="r-img" style="background:url(link/to/large/version); width:[width-of-image]px; height:[height-of-image]px;">
<img src="link/to/small/version" alt="" />
</div>
/* CSS */
.r-img img{
/* Hide image off-screen on larger devices, but leave it accessible to screen-readers */
position:absolute;
left:-9999px;
}
/*--- RESPONSIVE ---*/
@media(max-width:480px){
.r-img{
/* Remove styling from the div */
background:none!important;
width:auto!important;
height:auto!important;
}
.r-img img{
/* Bring smaller image back into view */
position:static;
max-width:100%;
}
}
Demo
I’ve made a little demo, try sizing your window down to see the functionality or, even better, visit it on your phone.
Also, open up Firebug’s Net tab and compare HTTP requests between the two versions. The background images, as you’d expect, just don’t get downloaded on the responsive version. Unfortunately, users on larger screens will still download both images…
Now, I did just think of this in the shower and wrote it straight down so please let me know of any potential stumbling blocks etc in the comments. Cheers!
Bonus
Instead of hiding the image off-screen we can actually set it to width:100% and height:100%; so it completely covers the background image and then set it to opacity:0;. This means that if a user right clicks the background image (to save it etc) they still can because they’re focussed on an invisible image in the page. See the second demo.
By Harry Roberts on Saturday, July 16th, 2011 in Web Development. Tags: Responsive web design | 31 Comments »
+
Thomas "Thasmo" Deinhamer said on 16 July, 2011 at 12:31 pm
Probably it’s possible to use the figure element instead of a div in HTML5?
Harry Roberts said on 16 July, 2011 at 12:34 pm
Hi Thomas,
A
figurewouldn’t be ideal here. Adivis still best as it lacks semantic meaning which is exactly what we want this to do. Putting it in afigurewould give it meaning that it’s not worthy of.Nick thorley said on 16 July, 2011 at 12:41 pm
Doesnt the user still have to download an image which is large doing your method – even if it’s hidden
Thasmo said on 16 July, 2011 at 12:44 pm
Devices with a big screen resolution will download both images, devices with a smaller screen resolution will only download the small image.
Thasmo said on 16 July, 2011 at 12:51 pm
I guess the CSS (this means also the CSS in the style attribute) is being processed after it has been downloaded completely, so the client does _not_ start to download the CSS background image, because it knows it is hidden at that point.
Ilia said on 16 July, 2011 at 3:03 pm
Looks pretty solid, and not really all that fiddly. At least not compared to other ways of doing this. The “bigger client” downloading the small image might be a problem on pages with a lot of images though.
I wonder, which image will load first, the background or the img tag?
Ben Demaree said on 16 July, 2011 at 4:46 pm
A quick look with IE9’s developer tools would seem to indicate that no matter the window size, it downloads all the images. Not really a bug in this case, as it’s not a mobile browser, but worth noting. Can anyone confirm?
Chrome and Firefox both seem to fetch the additional images only as required, as a mobile browser would.
Very cool technique. :-) Wish I was that productive in the shower.
@silvinci said on 16 July, 2011 at 11:55 pm
I just came up with a completely different Idea: Before all the stylesheets an other stuff you could include a in the which creates a Cookie with the screen resolution in it. Now the server can decide which images are sent to the client. In addition to that the that creates the cookie could just be included when the Cookie isn’t set. So you’re saving procession time on the client side.
Daniel newns said on 18 July, 2011 at 11:38 am
be interesting to see if mobile devices actually download the larger images, has anyone set this up on their server and looked at their logs to see if it comes down?
i might just give it a quick go.
Matthew said on 18 July, 2011 at 11:49 am
I think i would rather do this sort of thing server side than in the css and html then trust the browser to display the correct one. As @silvinci said it would be better to set a cookie with the screen res and then let the code on the server pick which image to present.
Kyle Shrives said on 18 July, 2011 at 12:13 pm
From what I understand, all images will still be downloaded. Keith Clark has been working on a cookie solution (not sure how far he is with it).
http://blog.keithclark.co.uk/responsive-images-using-cookies/
Zoran Jambor said on 18 July, 2011 at 2:16 pm
I’ve tried all major browser with this technique now, and it seems that Firefox, Chrome and IE9 work as expected. They download just the images that are necessarily.
However, Safari and Opera seem to download all of the images no matter the window size.
This is tested on desktop browsers, so it could be that their mobile equivalents work better, but that’s very unlikely.
Patrick Turmala said on 19 July, 2011 at 11:30 pm
Why not just use two img tags and “display:none” instead of the div?
Oncle Tom said on 20 July, 2011 at 7:31 am
Smart.
But as pointer earlier, it will download *both* images, which is just not a good thing to do for slow bandwidth as mobile device can have nowadays.
Christian Boyle said on 21 July, 2011 at 7:06 pm
How many of you guys are using 1000px images on your sites? Just curious.
sanntu said on 21 July, 2011 at 8:48 pm
No deja de ser una técnica válida, pero lo que estas optimizando para dispositivos móbiles, lo perdes en la optimización para navegadores de escritorio. ¿Porqué? Porque cargas las dos imagenes SIEMPRE.
La alternativa mas concreta ajustandome a lo expuesto, seria elegir la imagen de fondo segun el tamaño de pantalla, pero existe inevitablemente un error semántico. ¿Porqué? Porque las imagenes subidas por un administrador o un usuario son justamente eso, imagenes que reflejan contenido, y no son un “fondo”. Un fondo de imagen pierde relevancia con respecto a una imagen que refleja contenido.
Desconosco si se puede hacer mediante cookies, pero la idea especifica seria solo cargar UNA imagen dependiendo del escenario, sin cargar la imagen extra.
Google Translate:
No longer a valid technique, but what you’re optimizing for mobile devices, they lost in the optimization for desktop browsers. Why? Because the two images always load.
The most concrete alternative to the above setting, it would choose the background image according to screen size, but there is inevitably a semantic error. Why? Because the pictures uploaded by an administrator or a user are just that, images that reflect content and are not a “fund”. A background image loses relevance to a mirror image content.
Desconosco if you can do with cookies, but the idea would only be specified load an image depending on the scenario without extra load the image.
James said on 21 July, 2011 at 9:27 pm
This doesn’t really seem to be a perfect solution. Desktop users end up having to fetch both files anyway. Look at http://dvjs.us/resize/
This isn’t perfect either, but it fixes the double fetch for desktops?
@media(min-width:480px){
.desktop{background:url(desktop1.jpg);display:block!important;}
.mobile{background:none;display:none;}
}
@media(max-width:480px){
.desktop{background:none; display:none;}
.mobile{background:url(mobile1.JPG);display:block!important;}
}
paul said on 22 July, 2011 at 4:23 pm
good concept, but poor execution made it totally pointless
Dan said on 22 July, 2011 at 5:09 pm
Am I missing something? When I view the demo in Firefox (5.0.1) I get the same images now matter how small the window is.
brett said on 22 July, 2011 at 5:29 pm
is there any reason you could not use two different images? reason: I want a large image with some not-so-large type imposed onto the image. I think it would be too hard to read on may android screen. So I would want to just use the photo with a logo imposed on the photo.
Yoav Weiss said on 22 July, 2011 at 9:28 pm
I also think that double fetching of images on Desktop is a problem and James’ solution is better, since each device only downloads what it needs.
OTOH, moving all images to CSS will delay their fetching until the CSS is downloaded and parsed, crippling the browser’s look-ahead/preload parser ( http://blogs.msdn.com/b/ieinternals/archive/2011/07/18/optimal-html-head-ordering-to-avoid-parser-restarts-redownloads-and-improve-performance.aspx http://gent.ilcore.com/2011/01/webkit-preloadscanner.html ).
So either way, we wind up with a performance cost.
While these techniques have merit, we need a change in browser behavior/standards regarding this issue. I made my proposal regarding such a change at http://blog.yoav.ws/2011/05/My-take-on-adaptive-images
Harry Roberts said on 23 July, 2011 at 10:23 am
@Dan: This is because of a browser quirk type thing. If you scale FF down you’ll notice the Address and Google bars are fluid up until they close up completely, then the browser stops being fluid and just covers up the viewport.
I realise I explained that really badly but just size your browser down and watch the UI get non-fluid. This means the viewport can never reach the media-query I set.
H
Caroline Murphy said on 24 July, 2011 at 10:13 pm
I would have thought a cookie wasn’t the best way to serve an image, looking at the new EU directive.
It all seems a muchness, just to serve an image; I use a Blackberry Bold which is classed as a “smartphone” yet does not treat websites like these fancy iPhones et al.
I just turn images off. So in my opnion, serving two images to ‘desktop’ users seems overkill, for the sake of design.
http://www.mykeblack.com/seo/eu-cookie-directive
Carl said on 25 July, 2011 at 2:39 pm
Or you could just use content negotiation via WURFL: http://wurfl.sourceforge.net/.
John Darling said on 8 August, 2011 at 6:02 pm
@James
The problem with your method is that if a desktop user right clicks the image they will grab the background image not the image () due to the fact that you have it set to display:none. In this case , I like Harry’s option better.
The issue I see with the Harry’s solution is the for the desktop user. As other’s have said, they will have to download multiple images. What if you had several large images on the page? Do you sprite that large backgrounds or keep them as individual items. The server requests in this method are going to stack up quite quickly….thats the biggest concern for me. While its well known that many people today access the internet through mobile devices, its doesn’t make sense to penalize the desktop user.
Off the top of my head, I would say the better solution would be to check on the server side and deliver the content accordingly.
Harry, it all starts somewhere. Someone poses a concept/idea and it gets refined from there. Thanks for sharing. I am amused you thought of this in the shower :)
mairead said on 19 August, 2011 at 1:21 pm
Hey Harry, We were toying with the same ideas as you and discovered that if you hide the image in a noscript its hidden from the DOM and it prevents the HTTP request. You can see our article on the implementation here: http://www.headlondon.com/our-thoughts/technology/posts/creating-responsive-images-using-the-noscript-tag or check out our github repo: https://github.com/futurechimp/responsive_image_tag
David Sinclair said on 24 August, 2011 at 5:20 pm
Any thoughts on this solution?
• Have a library of large images
• Use a PHP script to convert these images on-demand to the proper size of a browser
• Have the HTML use only the output images
Some initial thoughts are that if a user resizes their browser, the images won’t be resizing, but it does keep content fit-to-size. Not sure if this would actually work though.
James said on 23 September, 2011 at 1:14 am
Love it. This is freaking sick, thanks for sharing.
Miguel said on 17 November, 2011 at 5:46 am
Thanks dude, I’m an amateur at this and had already spent all day trying to figure how to hide an image out. You saved me from wasting more time!!!!
Rik Hopkinson said on 29 February, 2012 at 1:41 pm
Quite a simple and nice solution. When I tested it on mobile it loaded up really quick and wasn’t looking for the bigger image. (as far as I could tell)
However – I tried just changing the image background link to the mobile sized version via media query for max-width: 480px and this seems to provide the same results but keeps it as a background image.
As far as I can see doing it that way it doesn’t load the big one either…
Anyone else tried that?
Daniel Eden said on 10 May, 2012 at 10:43 am
I was actually just trying to find a method similar to this that used just one element (which doesn’t seem to exist) when I stumbled upon yours, Harry.
I took this a little bit further and gave the container div `display: inline-block;` in order to more closely mimic the layout properties of `img`s.
Let me know what you think!