Unstoppable Robot Ninja

The site navigation:

Fluid Images

(Hello! If you think this article’s interesting, you might check out my ALA article on responsive web design.)

I’ve always hated publishing. I don’t mean the industry, but the act. Hitting “print,” sending an email, pressing that “Publish” button on the CMS: at some point, you relinquish your ability to smooth down some of the sharper edges, fill in the holes of your argument, and just generally fix whatever’s broken. At some point, you just accept what’s on the page, tousle its hair a bit, and send it off into the world at large to be judged, poked, and prodded. But that doesn’t mean you stop thinking about the article, what you could’ve done differently, and what you wished you’d included.

To wit:

One of the really solid criticisms lobbied against my Fluid Grids article for ALA was that all of my examples were pretty text-heavy. As a result, they all more or less ignored the issue I raised at the end of the essay: that working with non-fixed layouts can be more difficult once you introduce fixed-width elements into them. By default, an image element that’s sized at, say, 500px doesn’t exactly play nicely with an container that can be as large as 800px, but as small as 100px. What’s a designer to do?

In Which “Looking Across the Pond for Help” Is a Pretty Decent Answer, As It Turns Out

Since I started mucking around with this whole “stylesheets” thing, Richard Rutter has been one of those CSS giants on whose shoulders I frequently stand. His 62.5% technique was one I used for ages, and his work on sizing em-based text really provided the inspiration for cracking the whole “fluid grid” problem.

A few years back, Richard published a brilliant series of experiments with max-width and images, which I pored over when I was first working on this blog. Ultimately, I decided to use the approach from his third example, which was to set a max-width of 100% on all images on my website:

  1. img {
  2.      max-width: 100%;
  3. }

This solved the problem beautifully. Instead of just rendering at its native width and overflowing its containing box, the image would render at its native dimensions as long as its width didn’t exceed the width of its container. Here’s a quick test case I tossed together, using the text from one of my old blog entries as the image. Try resizing your browser window—looks great, right? Modern browsers are intelligent enough to keep the image’s proportions intact, even as the page scales up or down accordingly.

And as it turns out, this works just fine for most embedded videos, too:

  1. img,
  2. object {
  3.      max-width: 100%;
  4. }

So while this is shaping up nicely, we’re not quite done. As you may well know, older versions of Internet Explorer would rather smear poo in their hair than render a proper stylesheet don’t support max-width. So for especially large images, I’ve been relying on a simple width: 100% rule in an IE-specific stylesheet, like so:

  1. img {
  2.      width: 100%;
  3. }

With that, the larger issue was basically sorted: namely, that a non-fixed page’s layout won’t break if images or stupid Youtube movies were dropped into the markup. Hooray, right?

As it turns out, not quite. Not quite hooray at all.

In Which a Particular Platform Causes Issues, and While I Don’t Want to Call Anybody Out or Anything, Its Name Rhymes With “Blindows”

If you’ve been looking at the little test case on Windows, there’s a good chance that the image quality, well, looks awful as it scales down. The challenging bit is that this isn’t a browser-specific problem, but a platform-specific one: native image scaling on Windows just, well, kind of sucks. Specifically, if you’re on any version of Internet Explorer (prior to version IE8) or on a version of Firefox older than 3.0 on Windows, things look decidedly janky: the text gets painfully artifacted as the image resizes, beyond the point of maintaining any semblance of legibility. Thankfully, it’s fixable.

But first, the bad news: as far as I can tell, we have to write off Firefox 2 and below. While we could target those browsers on Windows with JavaScript, it’d be unfortunately reliant on user agent sniffing—and even if we wanted to go down that path, there’s no way to ask Firefox on Windows to toggle its “not horrendous” image rendering engine.

In Which I’m Actually Grateful for an IE-Specific, Non-Valid CSS Filter (I Know, I’m As Surprised As You Are)

Internet Explorer, however, does have such a toggle. When I was working on the (apparently discarded) W3C.org redesign last year, I discovered that Microsoft’s AlphaImageLoader property–the one we so frequently use to fix IE’s PNG transparency–kicks IE’s rendering engine into high gear…or at least into some acceptable level of not-suckage. (More recently, the Flickr team blogged about this very thing.) And as soon as I applied a PNG fix script to not just, well, PNGs but all images in the document, the image quality in IE improved sharply. The text looked crisp and legible, even at smaller window widths.

Sadly, it also caused another bug. Since the higher-quality AlphaImageLoader sort-of-object-thing sits between the image and its background, the scripts replace the src attribute of the img element with a transparent “spacer” GIF, and resizes to match the size of the original element. This hoop-jumping lets the AlphaImageLoader shine through. Unfortunately, this pretty much thwarts our max-width: 100%/width: 100% approach: since the GIF has its own physical dimensions (usually 1×1), it won’t really “scale” proportionally as its container changes shape. Instead, the spacer’s width will change dynamically, but its height is fixed at the initial value set by the PNG script.

So that’s why I whipped up this little script. In short, it cycles through your document, swaps out the images for a transparent GIF, and applies the AlphaImageLoader property to each one. Then, whenever the window’s resized, the script automatically recalculates the proper, proportional height and width of the image, and resizes the spacer graphic accordingly.

Here’s the script in full:

  1. var imgSizer = {
  2.      Config : {
  3.           imgCache : []
  4.           ,spacer : "/path/to/your/spacer.gif"
  5.      }
  6.      ,collate : function(aScope) {
  7.           var isOldIE = (document.all && !window.opera && !window.XDomainRequest) ? 1 : 0;
  8.           if (isOldIE && document.getElementsByTagName) {
  9.                var c = imgSizer;
  10.                var imgCache = c.Config.imgCache;
  11.                var images = (aScope && aScope.length) ? aScope : document.getElementsByTagName("img");
  12.                for (var i = 0; i < images.length; i++) {
  13.                     images.origWidth = images.offsetWidth;
  14.                     images.origHeight = images.offsetHeight;
  15.                     imgCache.push(images);
  16.                     c.ieAlpha(images);
  17.                     images.style.width = "100%";
  18.                }
  19.                if (imgCache.length) {
  20.                     c.resize(function() {
  21.                          for (var i = 0; i < imgCache.length; i++) {
  22.                               var ratio = (imgCache.offsetWidth / imgCache.origWidth);
  23.                               imgCache.style.height = (imgCache.origHeight * ratio) + "px";
  24.                          }
  25.                     });
  26.                }
  27.           }
  28.      }
  29.      ,ieAlpha : function(img) {
  30.           var c = imgSizer;
  31.           if (img.oldSrc) {
  32.                img.src = img.oldSrc;
  33.           }
  34.           var src = img.src;
  35.           img.style.width = img.offsetWidth + "px";
  36.           img.style.height = img.offsetHeight + "px";
  37.           img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')"
  38.           img.oldSrc = src;
  39.           img.src = c.Config.spacer;
  40.      }
  41.      // Ghettomodified version of Simon Willison's addLoadEvent() -- http://simonwillison.net/2004/May/26/addLoadEvent/
  42.      ,resize : function(func) {
  43.           var oldonresize = window.onresize;
  44.           if (typeof window.onresize != 'function') {
  45.                window.onresize = func;
  46.           } else {
  47.                window.onresize = function() {
  48.                     if (oldonresize) {
  49.                          oldonresize();
  50.                     }
  51.                     func();
  52.                }
  53.           }
  54.      }
  55. }

You can see it in action (screenshot of the working page in IE7), and download the script if you like. Simply update the path to your spacer graphic at the top of the code, invoke imgSizer.collate(); at window.onload (I prefer Simon Willison’s excellent addLoadEvent(), myself), and the script’ll apply the fix to all img elements on a page. Alternately, if you want to limit the scope, you can pass in a collection of img elements as an argument, thusly:

  1. addLoadEvent(function() {
  2.      if (document.getElementById && document.getElementsByTagName) {
  3.           var aImgs = document.getElementById("content").getElementsByTagName("img");
  4.           imgSizer.collate(aImgs);
  5.      }
  6. });

Drop max-width: 100% into your CSS, and rock the imgSizer script, and your fluid images should look impossibly fine.

In Which I Write a Pithy Conclusion Title

I wanted to write this up largely because, well, I think it could use a second pair of eyes. The technique works, but I could use any suggestions/criticisms you might have. It’s working well enough on my blog, and performed admirably on Airbag’s W3C.org redesign, but I’m sure there’s room for improvement. If you have any recommendations for how I could make this script Suck Less™, I’d love to hear it in the comments, or via email.

Thanks for reading.

This is a blog entry posted on day 11637 in the Journal.

50 comments posted.

50 Comments

  1. Scott says:

    Wow. A legitimate new front-end technique that solves a real problem in this day and age. Who’d have thunk it? : )

    Really interesting stuff, Ethan. It seems to work well. I’ll kick the tires some more and come back if I find any glitches.

  2. Olly says:

    Good stuff sir. Tell me, how does this fit into the equation?

    img { -ms-interpolation-mode: bicubic; }

    Brings a vast improvement in image-scaley-quality on IE/Win for me. Well, 7 upwards anyway.

  3. Bryan J Swift says:

    I have admittedly not attempted this technique but it would seem there is a different proprietary technique which could be employed without JavaScript for resizing images and maintaining image quality.

    The example provided on css-tricks looks good but I don’t know what happens if you continue to size the image down.

    Again, I haven’t tried to combine your fluid image sizing with the img { -ms-interpolation-mode: bicubic; } at css-tricks but it might be worth investigating.

  4. The Robot says:

    Hmm. As far as I can remember, -ms-interpolation-mode: bicubic didn’t work that well with images that didn’t have declared widths.

    I’ll have to have a play with the test case, and see if it works.

  5. Jason Robb says:

    Kudos! This looks solid, man. I’ll be kickin’ the tires, too.

    On another note, how weird is it that the W3C site is discarded? Very Weird™?

  6. Anton D Peck says:

    Bookmarked. This is really useful… especially with a fresh upcoming site that I’ve been working on. Thanks.

  7. Aaron Gustafson says:

    Well done sir!

  8. The Robot says:

    Okay, so I’ve tossed a quick test case up. From here, it looks as though -ms-interpolation-mode: bicubic definitely smooths out the rendering, but window resizing/repainting is noticeably slower. Like, “maybe this’d be a problem if I had more than one image on the page” levels of slower.

    Anyone else?

  9. Simmy says:

    You are a champion and a hero.

  10. Indranil says:

    woah, dude! Total buttkicking awesomeness!

  11. Jon B says:

    Instead of using a AlphaImageLoader based PNG fixing script have you considered this one – it is extremely good and addresses some of the main issues with the AlphaImageLoader route – however does have some of it’s own lol – I find myself switching between techniques for various layouts/PNGs.

  12. The Robot says:

    Jon B, I actually use DD_belatedPNG on URN, and you’re right: it’s great. However, I don’t think the VML object scales as well as the images do, if at all.

    But hey, if you want to whip up a test case, I’d love to see it.

  13. Bridget Stewart says:

    Wicked Awesome! Bookmarking this so I can find it as I need it. Thank you for putting the effort into this.

  14. jungshadow says:

    You are truly the more brilliant and more handsome Marcotte.

  15. David Sutoyo says:

    Great post. Just wanted to point out that when I clicked the link to the IE-bug image in Google reader, I got the “Get your own bandwidth” image. Works fine from this page, though.

  16. Tom H says:

    I had been using a similar method over the past few years that uses a Flash element with the image loaded behind the original… but this seems much nicer!

    I see two drawbacks:

    1. It breaks the ‘right click > save image as / view image’ browser functionality.
    2. It won’t work on Firefox, then again, FF3 does this automatically so this is largely moot.

    How can we fix 1? EIther:

    • Add another element behind the image with the image AlphaLoaded™ and make the image itself transparent.
    • Or find some way of delivering a spacer.gif to the browser but the original image when it’s directly downloaded or viewed.

    -ms-interpolation-mode: bicubic is great, but it only works on JPEGs :( Thanks Microsoft!

  17. The Robot says:

    That’s killer feedback, Tom! Thanks very much. And great catch on breaking the image saving functionality. Personally, I’m inclined to write that off as an acceptable loss, but others might not feel the same.

  18. Tom H says:

    Thanks. :) I’m working on a modified solution right this minute but my DOM manipulation isn’t working, can you see why?

    http://www.stainlessvision.com/lab/fluid-images/pngfix.html

    It basically wraps the image in a span that uses the AlphaLoader, then makes the child image invisible

  19. Bruno Abrantes says:

    This is definitely a most awesome solution. I’ll be adding this to my bookmarks and linking it to every one of my front-end loving friends.

    As Tom said, it’s a shame right-click support doesn’t work, but maybe that transparent image hack can do the trick. If not, “Save image as…” would work in all other browsers except IE, am I right? Still sounds like a good deal to me!

  20. Ray Brown says:

    Pretty nifty technique. Good work!

  21. Damjan Mozetič says:

    I just love it when shiny new posts like this come out. Thanks for the wisdom, it is totally appreciated. bows

  22. Kamal says:

    Nice Post sir,

    Thanks (:

  23. Olly Hodgson says:

    It gets more interesting. Now I’m in front of a Windows XP box or two, I thought I’d give it a try.

    In IE6 your script works a treat (apart from the right-click-save thing). Sadly I don’t have an IE7 box to hand, or I’d try that, too.

    In IE8, I don’t appear to need the script at all. Or -ms-interpolation-mode. It just works™.

  24. Olly Hodgson says:

    And another thing: For Tom H’s “Add another element behind the image with the image AlphaLoaded™ and make the image itself transparent.” could you use IE’s opacity filter? Or does that have a nasty effect on performance?

  25. Brajeshwar says:

    I’m not sure because I don’t interact with IE that much but have you tried the IE specific img interpolation mode to fix image scaling in IE.

    img { -ms-interpolation-mode: bicubic; }

  26. The Robot says:

    Olly, you’re right: IE8 does work fine without the script. I’ve updated the post to reflect that, and added a check for window.XDomainRequest to the script to keep IE8 from processing it. Of course, conditional comments would be a better option than checking for the presence of a completely unrelated object, but in the interest of making the above example self-contained, I’m going to take the ghettofabulous route.

    Plus, hey. It’s early here, and I’m low on caffeine.

  27. andrea says:

    Good script thanks!! Just a question. Can I use this script to autoresize a background image? If positive, how can I do that? I’ve searched the web but with confusing results…

  28. The Robot says:

    Andrea, that’s not quite what the script’s for. A variant could be used to properly scale an absolutely positioned image that sits “underneath” your content, but that’s a little out of scope for this article. Essay. Blog entry. Thing.

    Anyway, good luck!

  29. Daniel Gasienica says:

    Ethan,

    Great work on this! I myself have been tackling a different kind of problem in the same domain: How to integrate extremely large images within web sites? I’ve come with up a solution using an open source Flash multiscale image viewer from the OpenZoom project and a JavaScript replacement very much like you did. I’ve dubbed this technique Inline Multiscale Image Replacement:

    Coincidentally with the Flash solution I was also faced with the Save Image As…/View Image problem. However I was able to solve it using metadata in the multiscale image XML descriptor.

    I’d love to hear what you think about it!

  30. Justen Robertson says:

    Thanks man! My only complaint is that nobody came up with this sooner. I’ve struggled with and eventually abandoned this problem in the past. Also, as I’ve not posted here before, thanks for the great work you did on your layout here. it really inspires me to do flexible layouts more often. Also props on using textile. :)

  31. andz says:

    Why don`t you simply set position: absolute; width:100%; height: 100%; to image inside container?

  32. Joe says:

    Definitely great solution if you care. Personally I prefer to load a fixed-width stylesheet to those browsers not supporting quality image scaling, although I did not realise that FF2 was bad at it. It seems that it depends if you have font smoothing set to on in Windows (apparently – I don’t have Windows). I dislike using invalid css and loading a lot of hack scripts simply because someone is using Internet Exploder. It seems that everyone is spending twice as long making their sites perfect in IE, which in turn will never convince people to switch. If all IE users experienced “ugly” sites, then the whole IE dilema will go away for all of us, people would either want to see nice websites or don’t care. My stats are clearly showing that IE is not as popular as it used to be – hopefully this trend keeps going or even better, all IE’s just adhere to good standards like FF and Safari. At least now with release 8 of IE, MS is requesting everyone to upgrade.

    Great writeup – thank you.

  33. The Robot says:

    Joe, I’d refer you to Noah Stokes’ website. Learn it. Love it. Live it.

  34. Gyorgy says:

    Cool.
    It actually solves something. I so tired of articles that showcases something, but doesn’t solve anything. Same bullsh** everytime.

    I will watch this blog…

  35. Juarez P. A. Filho says:

    Wow! That’s really nice. I’m impressed. I don’t use fluid layouts because I always had problems with image scaling. Now I’ll change it.

    Keep moving. :)

  36. Marvin says:

    The Demo looks very promissing. Really great Solution, thanks for that! :)

  37. Dennison Uy says:

    To answer andz: because that will result in image distortion. And we all HATE image distortion. Right?

  38. Olly Hodgson says:

    Right, I’ve had a go at fixing the right-click-save problem with my own jquerificated version of the script. It clones the image, makes it invisible and bungs it in front of the smoothly-scaled version.

    I’d appreciate any feedback, comments, improvements, rewrites, blatant flames and giant piles of money anyone has to offer :)

  39. Beth says:

    Could it be true? My images won’t look out of place every time I change the width of my site’s layout?! Praise jeebus!

  40. Chris Wallace says:

    Well, grab a monkey’s bajongle and call me Sally. That’s a great script. Nicely done.

  41. The Robot says:

    Absolutely. Nicely done, Olly. The day’s a bit insane, but I’m going to see about folding your revisions into the original, non-jQuery script. Brilliant stuff!

  42. Aaron Mentele says:

    Love the idea (and the implementation) but I think there should be some kind of certification process developers need to pass before they’re cleared to load interface elements (imgs) at twice the default proportions and three times the filesize. I’ve always avoided the idea altogether out of concern for bandwidth. Does that make me some kind of… well, you know.

  43. The Robot says:

    I see your point, Aaron. But I’d argue this isn’t an entirely new practice: we already provide “oversized” elements for other aspects of our design. I’m thinking specifically of background images, which we’ll often make significantly larger when we can’t predict the dimensions of an element, or when we want to accommodate the user’s font resizing. Fluid images and fluid grids are, I believe, the next logical step in bulletproofing our designs, and offer the same usability benefits to the user.

  44. Module23 says:

    Thanks for sharing this brilliant article. Bookmarked for further use!

  45. Tom H says:

    After seeing the flurry of comments I’ve been re-inspired to fix up the code in my example here:

    http://www.stainlessvision.com/lab/fluid-images/pngfix.html

    So to re-iterate, this wraps the image in a span, makes the image invisible and sets the AlphaLoader on the span.

    I’m noticing some pixelated artefacts when scrolling, anyone else?

    I like the check Olly included in his code to see if the image has already been smoothed, this would allow someone to re-run the script if the page was updated dynamically with new images (a slideshow for example)… not something I’ve added here yet though.

  46. Sam says:

    I saw this script last week on your blog and have only just got round to using it and I have to congratulate you, works perfectly!

  47. Michel Fortin says:

    About the image interpolation problem, I may have a nicer trick than a script.

    When redesigning my website in 2005 I made one of these image and found that if your original image is a little more than the double in dimentions of the final rendering, it’ll scale well both on Windows and Mac OS X.

    To compensate the increase in file size I just blur the double-sized image a little and apply stronger JPEG compression. This results in a lower quality double-sized image, but once scaled down it’s quite fine.

    I wrote about it back then. The most inconvenient drawback is that it’s more work per image.

  48. The Robot says:

    That’s really interesting, Michel. I’m not sure how well it’d help images that have quite a bit of detail (e.g., text) in them, though—have you looked at the detail loss in files like that?

  49. klaus says:

    Curious how you can make this work with flash—trying to do the same scale effect?

    tried the object tag but i didn’t get it right :(

    thanks,

    klaus

  50. The Robot says:

    It should work basically the same way, Klaus. Just set the width of the object element the same way you would an img, and you should be set.

    Of course, the JavaScript won’t be required, since the movie should resize smoothly.

This was Ethan Marcotte’s Unstoppable Robot Ninja.

You are welcome.

Design and content © 2014 Ethan Marcotte. All rights reserved.

Photo copyright © Capcom.

Skip to the navigation, or skip to main content.