Fluid Images: "
JUNE 7, 2011
Fluid Images
by ETHAN MARCOTTE
We are pleased to present a portion of Chapter 3 of Responsive Web Design by Ethan Marcotte (A Book Apart, 2011). This chapter follows a previous chapter on fluid grids, expanding upon that grid with fluid images. —Ed.
Things are looking good so far: we’ve got a grid-based layout, one that doesn’t sacrifice complexity for flexibility. I have to admit that the first time I figured out how to build a fluid grid, I was feeling pretty proud of myself.
Fig 3.0: Our flexible grid is finally finished. not a pixel value in sight, and we didn’t have to skimp on the aesthetics.
But then, as often happens with web design, despair set in. Currently, our page is awash in words, and little else. Actually, nothing else: our page is nothing but text. Why is this a problem? Well, text reflows effortlessly within a flexible container—and I don’t know if you’ve noticed, but the Internet seems to have one or two of those “image” things lying around. None of which we’ve incorporated into our fluid grid.
So what happens when we introduce fixed-width images into our flexible design?
Going back, back to markup, markup
To find an answer, let’s do a quick experiment: let’s drop an image directly into our blog module, and see how our layout responds. The first thing we’ll need to do is to clear some space for it in our markup.
Remember our little
blockquote
, comfortably tucked into our blog article? Well, we’ve got way too much text on this darned page, so let’s replace it with an inset image: Lo, the robot walks
Nothing fancy: an
img
element, followed by a brief but descriptive caption wrapped in a b
element. I’m actually appropriating the HTML5 figure
/figcaption
tags as class names in this snippet, which makes for a solidly semantic foundation.(Sharp-eyed readers will note that I’m using a
b
element for a non-semantic hook. Now, some designers might use a span
element instead. Me, I like the terseness of shorter tags like b
or i
for non-semantic markup.)With that HTML finished, let’s drop in some basic CSS:
.figure { float: right; margin-bottom: 0.5em; margin-left: 2.53164557%; /* 12px / 474px */ width: 48.7341772%; /* 231px / 474px */ }
Fig 3.1: An appropriately botty robot pic,courtesy of Jeremy Noble.
We’re creating a nice inset effect for our figure. It’ll be floated to the right, and will span roughly half the width of our article, or four columns of our flexible grid. Markup: check; style: check. Of course, all this HTML and CSS is for naught if there isn’t an actual image available.
Now, because I love you (and robots) dearly, not just any image will do. And after scouring the web for whole minutes, I found a fantastically imposing robo-portrait (fig 3.1). The beautiful thing about this image (aside from the robot, of course) is that it’s huge. I’ve cropped it slightly, but I haven’t scaled it down at all, leaving it at its native resolution of 655×655. This image is much larger than we know its flexible container will be, making it a perfect case to test how robust our flexible layout is.
So let’s drop our oversized image onto the server, reload the page, and—oh. Well. That’s pretty much the worst thing on the internet.
Fig 3.2: Our huge image is huge. Our broken layout is broken.
Actually, the result isn’t that surprising. Our layout isn’t broken per se—our flexible container is working just fine, and the proportions of our grid’s columns remain intact. But because our image is much wider than its containing
.figure
, the excess content simply overflows its container, and is visible to the user. There simply aren’t any constraints applied to our image that could make it aware of its flexible environment.Fluid images
But what if we could introduce such a constraint? What if we could write a rule that prevents images from exceeding the width of their container?
Well, here’s the good news: that’s very easy to do:
img { max-width: 100%; }
First discovered by designer Richard Rutter, this one rule immediately provides an incredibly handy constraint for every image in our document. Now, our
img
element will render at whatever size it wants, as long as it’s narrower than its containing element. But if it happens to be wider than its container, then the max-width: 100%
directive forces the image’s width to match the width of its container. And as you can see, our image has snapped into place.Fig 3.3: Just by including
max-width: 100%
, we’ve prevented our image from escaping its flexible container. On a related note, I love max-width: 100%
.What’s more, modern browsers have evolved to the point where they resize the images proportionally: as our flexible container resizes itself, shrinking or enlarging our image, the image’s aspect ratio remains intact.
Fig 3.4: Regardless of how wide or small its flexible container becomes, the image resizes proportionally. Magic? Who can say.
I hope you’re not tired of all this good news because as it happens, the
max-width: 100%
rule can also apply to most fixed-width elements, like video and other rich media. In fact, we can beef up our selector to cover other media-ready elements, like so: img, embed, object, video { max-width: 100%; }
Whether it’s a cute little Flash video, some other embedded media, or a humble
img
, browsers do a fair job of resizing the content proportionally in a flexible layout. All thanks to our lightweight max-width constraint.Fig 3.5: Other media play nicely with
max-width: 100%
, becoming flexible themselves. Did I mention I love max-width: 100%
?So we’ve cracked the problem of flexible images and media—right? One CSS rule and we’re done?
Because this job is never easy
Time to let the healing begin: we need to work through the pain, the tears, the rending of garments, and talk about a few browser-specific issues around flexible images.
MAX-WIDTH IN INTERNET EXPLORER
The cold, hard truth is that Internet Explorer 6 and below don’t support the
max-width
property. IE7 version and above? Oh, it is positively brimming with support for max-width
. But if you’re stuck supporting the (cough) venerable IE6 or lower, our approach needs refinement.Now, there are several documented ways to get
max-width
support working in IE6. Most are JavaScript-driven, usually relying on Microsoft’s proprietary expression filter to dynamically evaluate the width of an element, and to manually resize it if it exceeds a certain threshold. For an example of these decidedly non-standard workarounds, I’d recommend Svend Tofte’s classic blog entry on the subject.Me? I tend to favor a more lo-fi, CSS-driven approach. Namely, all modern browsers get our
max-width
constraint: img, embed, object, video { max-width: 100%; }
But in a separate IE6-specific stylesheet, I’ll include the following:
img, embed, object, video { width: 100%; }
See the difference? IE6 and lower get
width: 100%
, rather than the max-width: 100%
rule.A word of warning: tread carefully here, for these are drastically different rules. Whereas
max-width: 100%
instructs our images to never exceed the width of their containers, width: 100%
forces our images to always match the width of their containing elements.Most of the time, this approach will work just fine. For example, it’s safe to assume that our oversized
robot.jpg
image will always be larger than its containing element, so the width: 100%
rule works beautifully.But for smaller images like thumbnails, or most embedded movies, it might not be appropriate to blindly up-scale them with CSS. If that’s the case, then a bit more specificity might be warranted for IE:
img.full, object.full, .main img, .main object { width: 100%; }
If you don’t want the
width: 100%
rule to apply to every piece of fixed-width media in your page, we can simply write a list of selectors that target certain kinds of images or video (img.full
), or certain areas of your document where you know you’ll be dealing with oversized media (.main img
, .main object
). Think of this like a whitelist: if images or other media appear on this list, then they’ll be flexible; otherwise, they’ll be fixed in their stodgy old pixel-y ways.So if you’re still supporting legacy versions of Internet Explorer, a carefully applied
width: 100%
rule can get those flexible images working beautifully. But with that bug sorted, we’ve still got one to go.And boy, it’s a doozy.
IN WHICH IT BECOMES CLEAR THAT WINDOWS HATES US
Fig 3.6: Seen here in IE6, our robot image has developed some unsightly artifacts. Guess Windows doesn’t much care for our flexible images.
If you look at our blog module with certain Windows-based browsers, our
robot.jpg
has gone from looking imposing to looking, well, broken). But this isn’t a browser-specific issue as much as a platform-specific one: Windows doesn’t scale images that well. In fact, when they’re resized via CSS, images quickly develop artifacts on Windows, dramatically impacting their quality. And not in a good way.For a quick test case, I’ve tossed a text-heavy graphic into a flexible container, and then resized our image with the
max-width: 100%
fix, while IE6 and below receive the width: 100%
workaround. Now, you’d never actually put this amount of text in an image. But it perfectly illustrates just how badly things can get in IE7 or lower. As you can see, the image looks—if you’ll pardon the technical term—downright nasty.But before you give up on the promise of scaleable, flexible images, it’s worth noting that this bug doesn’t affect every Windows-based browser. In fact, only Internet Explorer 7 and lower are affected, as is Firefox 2 and lower on Windows. More modern browsers like Safari, Firefox 3+, and IE8+ don’t exhibit a single problem with flexible images. What’s more, the bug seems to have been fixed in Windows 7, so that’s more good news.
Fig 3.7: In certain Windows-based browsers, the image quickly develops too many artifacts to be readable.
So with the scope of the problem defined, surely there’s a patch we can apply? Thankfully, there is—with the exception of Firefox 2.
Now, this grizzled old browser was released in 2006, so I think it’s safe to assume it isn’t exactly clogging up your site’s traffic logs. At any rate, a patch for Firefox 2 would require some fairly involved browser-sniffing to target specific versions on a specific platform—and browser-sniffing is unreliable at best. But even if we did want to perform that kind of detection, these older versions of Firefox don’t have a switch that could fix our busted-looking images.
Internet Explorer, however, does have such a toggle. (Pardon me whilst I swallow my pride for this next section title.)
HAIL ALPHAIMAGELOADER, THE CONQUERING HERO
Ever tried to get transparent PNGs working in IE6 and below? Chances are good you’ve encountered
AlphaImageLoader
, one of Microsoft’s proprietary CSS filters. There have since been more robust patches created for IE’s lack of support for the PNG alpha channel (Drew Diller’s DD_belatedPNG library is a current favorite of mine), but historically, if you had a PNG attached to an element’s background, you could drop the following rule into an IE-specific stylesheet: (Line wraps marked » —Ed.) .logo { background: none; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader» (src="/path/to/logo.png", sizingMethod="scale"); }
This
AlphaImageLoader
patch does a few things. First, it removes the background image from the element, then inserts it into an AlphaImageLoader
object that sits “between” the proper background layer and the element’s content. But the sizingMethod
property is the clever bit, dictating whether the AlphaImageLoader object should crop any parts of the image that overflow its container, treat it like a regular image, or scale it to fit it within its containing element.I can hear you stifling your yawns by now: after all, what does an IE-specific PNG fix have to do with our broken image rendering?
Quite a bit, as it turns out. At one point I discovered that applying
AlphaImageLoader
to an image dramatically improves its rendering quality in IE, bringing it up to par with, well, every other browser on the planet. Furthermore, by setting the sizingMethod
property to scale
, we can use our AlphaImageLoader
object to create the illusion of a flexible image.So I whipped up some JavaScript to automate that process. Simply download the script and include it on any page with flexible images; it will scour your document to create a series of flexible, high-quality
AlphaImageLoader
objects.And with that fix applied, the difference in our rendered images is noticeable: in our example we’ve gone from an impossibly distorted image to an immaculately rendered one. And it works wonderfully in a flexible context.
Fig 3.8: Our image is now perfectly legible, and resizing wonderfully. A dab of
AlphaImageLoader
’ll do ya.(It’s worth mentioning that many of Microsoft’s proprietary filters, and
AlphaImageLoader
in particular, have some performance overhead associated with them—Stoyan Stefanov covers the pitfalls in more detail on the YUI blog. What does this mean for you? Just be sure to test the fix thoroughly on your site, gauge its effect on your users, and evaluate whether or not the improved rendering is worth the performance tradeoff.)With the
max-width: 100%
fix in place (and aided by our width: 100%
andAlphaImageLoader
patches), our inset image is resizing beautifully across our target browsers. No matter the size of the browser window, our image scales harmoniously along with the proportions of our flexible grid.But what about images that aren’t actually in our markup?
Flexibly tiled background images
Let’s say our dearly esteemed designer sends over a revised mockup of our blog module. Notice anything different about it?
Fig 3.9: Our blog’s sidebar is now sporting a background graphic. Hot.
Up until now, our blog’s content has been sitting on a rather unassuming near-white background. But now the design has been modified slightly, adding a two-toned background to the blog entry to provide more contrast between the left- and right-hand columns. What’s more, there’s actually a subtle level of noise added to the background, adding an extra level of texture to our design:
Fig 3.10: A detailed look at our new background treatment.
So: how do we actually add this new background image to our template?
Back in 2004, Dan Cederholm wrote a brilliant article showing how a vertically repeating background graphic could be used to create a “faux column” effect. The technique’s genius is in its simplicity: by tiling a colored background graphic vertically behind our content, we can create the illusion of equal height columns.
In Dan’s original technique, the background graphic was simply centered at the top of the content area and then tiled vertically, like so:
.blog { background: #F8F5F2 url("blog-bg.png") repeat-y 50% 0; }
And that technique works beautifully. But Dan’s technique assumes that your design is a fixed width, creating a graphic that matches the width of your design. Then how, pray, are we supposed to work in a background image that tiles over two flexible columns?
Thanks to some early research by designer Doug Bowman, we can still apply the faux column technique. It just requires a little bit of extra planning, as well as a dash of your favorite formula,
target ÷ context = result
.First, we’ll begin by taking a look at our mockup, to find the transition point in our background graphic, the exact pixel at which our white column transitions into the gray. And from the look of things, that switch happens at the 568 pixel mark.
Fig 3.11: Our white column switches over to gray at the 568px mark. That’s our transition point.
Armed with that information, we can now adapt the “faux columns” approach to our fluid grid. First, we’ll convert that transition point into a percentage-based value relative to our blog module’s width. And to do so, our
target ÷ context = result
formula comes into play yet again. We have our target value of 568px
, and the width of the design—our context—is 900px. And if we plug those two values into our stalwart formula:568 ÷ 900 = 0.631111111111111
That’s right: another impossibly long number, which converts to a percentage of
63.1111111111111%
.Keep that percentage in the back of your mind for a moment. Now, let’s open up your favorite image editor, and create a foolishly wide document—say, one that’s 3000 pixels across. And since we’re going to tile this image vertically, its height is only
160px
tall.Fig 3.12: A monstrously large canvas that we’ll (shortly) turn into our background graphic.
In a moment, we’re going to turn this blank document into our background graphic. But why is it so large? Well, this image needs to be larger than we can reasonably assume the browser window will ever be. And unless you’re reading this from the 25th century on your wall-sized display made of, I don’t know, holograms or whatever, I’m assuming your monitor’s not quite that wide.
To create the columns themselves, we’ll need to apply the transition point percentage (
63.1111111111111%
) to our new, wider canvas. So if we’re working with a graphic that’s3000px
across, we simply need to multiply that width by the percentage, like so:3000 x 0.631111111111111 = 1893.333333333333
We’re left with
1893.333333333333
as our result. And since Photoshop doesn’t deal in anything less than whole pixels, let’s round that down to 1893 pixels. Armed with that number, we’ll recreate our textures in our blank image, switching from white to gray at the 1893rd pixel.Fig 3.13: We’ve applied that percentage to our oh-so-wide background graphic, creating our tile-ready columns.
How does that help us? Well, what we’ve just done is to proportionally scale our transition point up to this new, wider canvas. So we can take that new pixel value, and use it to create our columns: the white column will be
1893px
wide, with the gray column filling up the remainder.So now there’s only one thing left to do: drop our newly minted graphic into our stylesheet.
.blog { background: #F8F5F2 url("blog-bg.png") repeat-y 63.1111111111111% 0; /* 568px / 900px */ }
As in Dan’s original technique, we’re still positioning the graphic at the very top of our blog, and then repeating it vertically down the width of the module (
repeat-y
). But thebackground-position
value reuses our transition point percentage (63.1111111111111% 0
), keeping the columns firmly in place as our design resizes itself.And with that, we’ve got faux columns working beautifully in a fluid layout. All thanks to Dan Cederholm’s original approach, augmented with a little proportional thinking.
fig 3.14: Our flexibly faux columns.
FULLY FLEXIBLE BACKGROUND IMAGES?
Of course, our flexible faux column isn’t really flexible: we’re simply using percentages to position a background image in such a way that the columns appear to resize with their container. The image’s dimensions haven’t changed at all.
But what about a background image that actually does need to resize with the layout? Perhaps you’ve placed a logo on an
h1
element’s background, or used sprites to create rollovers for your site’s navigation. Can we resize images that need to live in the background?Well, sort of. There is a CSS3 property called
background-size
, which would allow us to create truly flexible background images, but—you guessed it—browser support is still pretty immature.In the interim, there are some rather ingenious JavaScript-based solutions out there: for example, Scott Robbin’s jQuery Backstretch plugin simulates resizable background images on the body element. And as you’ll see in the next chapter, CSS3 media queries could also be used to apply different background images tailored to different resolution ranges. So while
background-size
might not be available yet, the sky is, as the kids say, the limit. About the Author
Ethan Marcotte is a web designer & developer who cares deeply about beautiful design, elegant code, and the intersection of the two. Over the years, Ethan has enjoyed working with such clients as the Sundance Film Festival, Stanford University, New York Magazine and The Today Show. He swears profusely on Twitter, and would like to be an unstoppable robot ninja when he grows up.
"