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:
- img {
- max-width: 100%;
- }
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:
- img,
- object {
- max-width: 100%;
- }
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:
- img {
- width: 100%;
- }
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:
- var imgSizer = {
- Config : {
- imgCache : []
- ,spacer : "/path/to/your/spacer.gif"
- }
- ,collate : function(aScope) {
- var isOldIE = (document.all && !window.opera && !window.XDomainRequest) ? 1 : 0;
- if (isOldIE && document.getElementsByTagName) {
- var c = imgSizer;
- var imgCache = c.Config.imgCache;
- var images = (aScope && aScope.length) ? aScope : document.getElementsByTagName("img");
- for (var i = 0; i < images.length; i++) {
- images.origWidth = images.offsetWidth;
- images.origHeight = images.offsetHeight;
- imgCache.push(images);
- c.ieAlpha(images);
- images.style.width = "100%";
- }
- if (imgCache.length) {
- c.resize(function() {
- for (var i = 0; i < imgCache.length; i++) {
- var ratio = (imgCache.offsetWidth / imgCache.origWidth);
- imgCache.style.height = (imgCache.origHeight * ratio) + "px";
- }
- });
- }
- }
- }
- ,ieAlpha : function(img) {
- var c = imgSizer;
- if (img.oldSrc) {
- img.src = img.oldSrc;
- }
- var src = img.src;
- img.style.width = img.offsetWidth + "px";
- img.style.height = img.offsetHeight + "px";
- img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')"
- img.oldSrc = src;
- img.src = c.Config.spacer;
- }
- // Ghettomodified version of Simon Willison's addLoadEvent() -- http://simonwillison.net/2004/May/26/addLoadEvent/
- ,resize : function(func) {
- var oldonresize = window.onresize;
- if (typeof window.onresize != 'function') {
- window.onresize = func;
- } else {
- window.onresize = function() {
- if (oldonresize) {
- oldonresize();
- }
- func();
- }
- }
- }
- }
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:
- addLoadEvent(function() {
- if (document.getElementById && document.getElementsByTagName) {
- var aImgs = document.getElementById("content").getElementsByTagName("img");
- imgSizer.collate(aImgs);
- }
- });
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.
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞