Flexible Scalable Background Image
20 May 09
When I get Photoshop comps from designers to build in HTML, CSS and Javascript, there are inevitably some elements of the designs which work well in the Ivory Tower of Photoshop, but present unique challenges when translating those elements into code a browser can work with. One such element on a project I recently completed featured a fixed size content area layered over background image that took up the entire browser window. The comp looked something like this:

The most striking aspect of the design, I think, is the full-window background image. When thinking about how to implement it, I considered what the client wanted as well as how I thought it should behave. When I combined those, the requirements I listed out for it seemed a bit daunting. It would have to…
- Fill up the browser window, regardless of the window size.
- Maintain the aspect ratio of the image (so it doesn’t distort like in a funhouse mirror).
- Resize with the browser window as the window got bigger.
- Maintain a minimum size that it would not shrink beyond.
- Remain centered.
- Not cause scrollbars.
- Not be in the HTML as an
<img />element.
Now, I know this ground has been tread before, but the solutions I came across didn’t fulfill all of the requirements I needed. There was no way CSS was going to do it alone, even if I used CSS3’s mythical marginally supported background-size property.
The Basics
Let’s consider my last requirement first: Not be in the HTML as an <img /> element.
Since the image is a supporting design element, rather than integral to the content on the page, this was an important consideration. I didn’t want the template HTML to be cluttered up with a big image which wouldn’t make sense if you were to view the page with, say, a browser on a mobile phone. It would have to start with a background image on the <body> element. I began by setting up the screen CSS with a definition like this:
body {margin: 0;padding: 0;background-image: url(img/bg_page_default.jpg);background-position: top center;background-repeat: no-repeat;background-color: #333;/* This next one is for IE6 */background-attachment: fixed;}
For the project, I decided that the background images would be sized to 1280×960. With that CSS definition above, if you look at the site with a monitor that has that resolution or smaller, you’d see the image fill the window, remain centered, not cause scrollbars and basically fulfill all the requirements I listed above. All would be well.
But what if your monitor is bigger? What if it’s wider, taller or both? In that case, if you were to expand your browser window beyond 1280 pixels wide or 960 pixels tall, you’d see the dark grey background color, and that’s no good, because it would violate my first requirement: Fill up the browser window, regardless of the window size. It ends up looking something like this:

Clearly, we’re falling short so far.
Enhancing it for the Big Screen
To reiterate my 3rd and 4th requirements:
- Resize with the browser window as the window got bigger.
- Maintain a minimum size that it would not shrink beyond.
For simplicity’s sake, I decided that the minimum image size for the background would be the native size of the image itself (in this case, 1280×960 pixels). With the window any smaller than that, you’d see a portion of the image. It wouldn’t resize, and it would remain centered.
As the window gets bigger then that on one or both axes, though, we have to stretch the image to fill the available space. Since we can’t do that reliably with CSS yet, a different approach needs to be taken. I needed some Javascript to fill the void. Armed with it, I would:
- Grab the URL from the
background-imageproperty on the<body>. - Use that URL to create an
<img />element which I’d then append to the<body>element. - Hide the new
<img />element when the browser window is smaller than the image. - Show the new
<img />element when the browser window is either taller or wider than the image. - Set the new
<img />element to either 100% wide or 100% high (but not both!), as necessary, to maintain the aspect ratio.
In order to take care of steps 3, 4 and 5, I opted to use some CSS to do that rather modifying CSS properties directly on the generated <img /> element, so I set up some definitions like so:
img#expando {padding: 0;margin: 0;position: absolute;display: none;z-index: 1;-ms-interpolation-mode: bicubic;}.wide img#expando,.tall img#expando {display: block;}.wide img#expando {width: 100%;height: auto;}.tall img#expando {width: auto;height: 100%;}.ie6fixed {position: absolute;top: expression((ignoreMe = document.documentElement.scrollTop ?document.documentElement.scrollTop : document.body.scrollTop) + 'px');}
Now, with those definitions, I can simply toggle a wide or tall class name on the <body> element to hide and show the generated image, as well as determine whether it should stretch horizontally or vertically. I’ll set up a Javascript function to execute when the window gets resized which does the calculation and applies the necessary class name to the <body> element.
As an aside, if you’re curious about the -ms-interpolation-mode: bicubic property, that’s in there to keep the image from looking, as Ethan puts it, janky in IE7 when it’s resized. You can peruse the geeky details on MSDN.
I think it’s important to point out that the first 2 steps above respect my first requirement – not be in the HTML as an <img /> element. Because we’ll use the URL of the <body>’s background image to create a new image element on the fly with Javascript, the image won’t be appended to the page if we don’t have Javascript available or our CSS doesn’t define a background image on the body, as might be the case if you had CSS files for alternate environments, like mobile phones for instance.
One detail I didn’t list out is that in order to respect my not cause scrollbars requirement, I’d have to wrap the generated image with a <div></div>. I’ll set that wrapper <div></div>’s width and height to 100% of the browser window’s width and height and apply an overflow: hidden to it, which will crop the image and prevent scrollbars from appearing. I’ll take care of this in the Javascript, as I only need to set those styles when the wrapper and the image are created.
Where it All Comes Together
So here’s a version of the script I came up with that pulls together all that functionality in its entirety:
var flexiBackground = function(){/**CONFIGURATION:Define the size of our background image*/var bgImageSize = {width : 1280,height : 960};/**Declare and define variables*/var $window = $(window),$body = $('body'),imageID = "expando",tallClass = 'tall',wideClass = 'wide',$bgImage, $wrapper, img, url, imgAR;/**Are we dealing with ie6?*/var ie6 = ($.browser.msie && parseInt($.browser.version, 10) <= 6);/**Set up the action that happens on resize*/var resizeAction = function() {var win = {height : $window.height(),width : $window.width()};// The current aspect ratio of the windowvar winAR = win.width / win.height;// Determine if we need to show the image and whether it needs to stretch tall or wideif (win.width < bgImageSize.width && win.height < bgImageSize.height) {$body.removeClass(wideClass).removeClass(tallClass);} else if ((win.w < bgImageSize.width && win.height >= bgImageSize.height) || (winAR < imgAR)) {$body.removeClass(wideClass).addClass(tallClass);// Center the image$bgImage.css('left', Math.min(((win.width - bgImageSize.width) / 2), 0));} else if (win.width >= bgImageSize.width) {$body.addClass(wideClass).removeClass(tallClass);$bgImage.css('left', 0);}// Need to fix the height of the wrapper for IE6if (ie6) {$wrapper.css('height', win.height);}};return {/*Sets up the basic functionality*/initialize : function() {// No need for any of this if the screen isn't bigger than the background imageif (screen.availWidth <= bgImageSize.width || screen.availHeight <= bgImageSize.height) {return;}// Parse out the URL of the background image and drop out if we don't have oneurl = $body.css('background-image').replace(/^url\(("|')?|("|')?\);?$/g, '') || false;if (!url || url === "none" || url === "") {return;}// Get the aspect ratio of the imageimgAR = bgImageSize.width / bgImageSize.height;// Create a new image element$bgImage = $('<img />').attr('src', url).attr('id', imageID);// Create a wrapper and append the image to it.// The wrapper ensures we don't get scrollbars.$wrapper = $('<div></div>').css({'overflow' : 'hidden','width' : '100%','height' : '100%','z-index' : '-1'}).append($bgImage).appendTo($body);// IE6 Doesn't do position: fixed, so let's fake it out.// We'll apply a class which gets used in the CSS to emulate position: fixed// Otherwise, we'll simply used position: fixed.if (ie6) {$wrapper.addClass('ie6fixed');} else {$wrapper.css({'position' : 'fixed','top' : 0,'left' : 0});}// Set up a resize listener to add/remove classes from the body$window.bind('resize', resizeAction);// Set it up by triggering a resize$window.trigger('resize');}};}();$(document).ready(flexiBackground.initialize);
Yes, yes, it’s using jQuery to help smooth out some of the rougher areas of Javascript like event handling and reading the CSS properties of elements, so if you’d like to use it as is, be sure to include jQuery on any page that uses this script as well (If you prefer, the script can fairly easily be ported to utilize a different framework or, with a little extra effort, none at all).
The project I’ve been referencing throughout this post is using a version of this script successfully – take a look at it in context. You can also take a look at a working example or download the script for your own use. Make sure that you include the CSS definitions above!
A Word of Warning
The resizeAction() method gets tied to the resize event on the browser window. Most browsers fire the event repeatedly as you resize a window, giving you a smooth transition between when you see the <body> background image and the generated stretchy <img />. Some browsers, however, wait until after you’re done resizing to fire the event, giving a decidedly ugly transition between the two. The most notable offender is Firefox and other Gecko based browsers. This is a long standing bug, going back to the Netscape 4 days(!), and it looks as if the Mozilla team has finally fixed it in the upcoming Firefox 3.1/3.5 release.
In Parting
If you’ve found this useful, I’d love to hear your thoughts on how it could be improved upon and any ways you take it and make it your own. Either leave a comment or email me.








Comments
Guillermo says:
264 days ago ∞
This seems like a solid solution. Can’t wait to try it out.
Marc Amos says:
264 days ago ∞
REALLY nice example here, Mike. Great work, and thanks for documenting it so clearly.
Mike Kitchens says:
254 days ago ∞
Great solution for IE, but the latest Firefox really doesn’t like this at all. It places the image on-top of the content, unfortunately…well, at least the way I implemented it. Of course, I’m trying to use it with a vBulletin install, so that may be the issue. Unfortunate. Also, I can’t seem to get it to expand height wise. It doesn’t resize to the entire screen. :(
Michael Bester says:
253 days ago ∞
@Mike: I see – that was a z-indexing problem that I had in there. I’ve updated the script to set the generated background image with a negative z-index. It should now ensure that the background stays behind the content, not in front of it, as was happening in Firefox.
Grab the script again, and you should be good to go.
A word of caution, though: Firefox 2 doesn’t support negative z-indexing, so if you’re supporting that browser, you’ve been warned! It shouldn’t be much of an issue now, though, almost all Firefox users are using FF3 by now.
bradleysnider says:
231 days ago ∞
Thanks you saved the day!
John Rogerson says:
231 days ago ∞
This looked a great and elegant solution to the semantically problematic solution of having to add an <img…> to your markup. However, I discovered that your method breaks if content is longer than the viewport. Essentially, the background image stops scaling at the bottom of the viewport when you scroll beyond that point to view the rest of your content. Seemed to be consistent cross browser too. Is this something you overlooked or is there something I haven’t taken into account? Sorry, but I haven’t got an example to demonstrate this (private dev server), but it should be pretty simple to replicate.
Michael Bester says:
231 days ago ∞
@John — You are indeed correct. That was a glaring oversight on my part, as the site I implemented it on uses a fixed height content window, so it never got a vertical scrollbar.
I’ve updated the script to allow for any length content. For most browsers, it was simply a matter of using
position: fixedon the image wrapper. IE6, however, needed a little extra help, so there are also some new CSS rules that allow it to emulateposition: fixed, as it doesn’t do it natively.John Rogerson says:
229 days ago ∞
Beautiful, works like a charm. I was transfixed on background-attachment being the root of this problem; didn’t take into account the wrapper.
meylo says:
224 days ago ∞
Is it possible to randomize the background img using this method?
Michael Bester says:
224 days ago ∞
@meylo – This method takes whatever you have set for the background-image property on the body element in your CSS. You could randomize that property to achieve what you’re looking for.
NYcc says:
216 days ago ∞
Apologies if this is super basic… I’m not the most advanced.
I’ve been able to set randomized background images and to use what you did to have a scalable background image (thank you!). But when I try to combine the two – disaster strikes. Is there a painfully obvious way to have both a randomized, scalable background – that I am just missing?
meylo says:
216 days ago ∞
@Michael Bester – thanks I see what I can dig up for that…
sonambvlo says:
193 days ago ∞
thanks for this, 1st time having to do this in html (i usually work in flash)
Jonathan Miller says:
189 days ago ∞
None of the examples I see here on your post work. I checked them with Safari 4 and FF3, no luck. I tested it locally as well. It looks like there is an error here: <code>var $window = $(window),</code>
Michael Bester says:
189 days ago ∞
@Jonathan Miller — I’m not sure why it isn’t working for you. I tested the examples in Firefox, Safari and the IEs when I published this post, and I tested them again just now, and they’re working just fine. Maybe for some reason jQuery isn’t getting loaded in the examples when you see them?
Jonathan Miller says:
189 days ago ∞
I’m still not seeing the effect work. I made sure jQuery was loading, which incidentally fixed the error with the window, but still no luck.
I viewed the source of the page as it was running and noticed that the wrapper div and the image had not been added to the page by the script. For whatever reason it is failing to do this for me.
Michael Bester says:
189 days ago ∞
@Jonathan – The examples actually require a large monitor in order to see the effect. They’re set up so that it only appends the flexible image if a user’s screen is larger than 1280px wide or 960px tall.
If you use a smaller background image (say, 800×600), and change the
bgImageSizeconfiguration option in the script, you’ll see the intended effect.George says:
185 days ago ∞
Hi Michael,
Is it possible to toggle though different background images with “previous” & “next” <a href> tags without using php? Or would I need to place the image in the HTML? I have some experience with java but almost no experience with JQuery.
Here’s an example of what I’m kind of going for:
http://www.thereevesagency.com/work.php?id=1
Michael Bester says:
185 days ago ∞
@George — You could do it with Javascript only, but that’s a considerable amount of additional functionality beyond what this script offers. The better way to do what you’re looking for is almost exactly what the demo URL you reference does – has individual HTML files for each Previous and Next item. It doesn’t need to be PHP – it could be straight HTML, but a different page per picture is really the way to go.
John Rogerson says:
180 days ago ∞
This is a strange one. This could be just an issue related to the site I’m working on and not general but here goes: on the Mac platform, cross browser, everything works fine. However, on Windows, cross browser (!), I came across an issue where my square image (1024×1024) would not stretch at screen resolutions above 1024×768 (this is usually the screen res I’ve got set in my Virtual environment). Going by Firebug et al, the script simply wasn’t firing and the relevant classes weren’t being attached to
<body>etc. I only came across this when I saw the site in a 1280×1024 environment. I eventually whittled down the dimensional settings inbgImageSizeto 680×680 (but leaving the actual image file untouched at 1024×1024), to where it seemed a multiple of screen resolutions would work (e.g. 1152×864, 1280×720, 1280×768 … 1280×1024). Due to time constraints and questions of sanity I wasn’t really able to test various viewports within these resolutions, but a couple of quick resizings here and there seemed to come out ok. So, this may be the source of the issues that Jonathan may have been having earlier. Dunno if there’s a solution to this beyond the sizing hack I came up with, but would be great to know if there’s a script based solution.zach says:
176 days ago ∞
How can I use this script, but add the logic of random images being generated for each page?
Michael Bester says:
176 days ago ∞
@Zach — Please have a look at my earlier comment.
zach says:
168 days ago ∞
What image size should we be using?
Michael Bester says:
168 days ago ∞
@Zach – It can be whatever size you want, but you should keep a few suggestions in mind:
bgImageSizeconfiguration at the top of the script to match your image.cstewz says:
153 days ago ∞
Hi Michael,
I’ve been looking for a solution to this design element for a little while now and think this solution is fantastic. Nice work, I’ll be sure to give you a mention when my designer throws this kind of design my way.
Will says:
145 days ago ∞
Thanks for this solution. When it works, it works beautifully.
However, I noticed the same bug that John did. Across all browsers on this PC I found that the script simply isn’t firing on the provided examples. Tested it on a Mac, and it works just fine. I tried the same img resizing hacks without much/any success. Ideas?
Dave Wilson says:
142 days ago ∞
Okay, I’m really new at this stuff, where exactly do I put this? Do I paste it into a new .js file that I reference in my HTML body tag or do I add it as a script element before my body tag? I must be doing something wrong cause it’s not working for me. I’ve added the background-image tag to my CSS file and the image loads into my page but it doesn’t resize at all. I“m on a 1280×1024 screen so I’m using an 800×600 image to test it out and the image is tiling in my test file.
You can see here http://www.secondsightstudio.com/indextemp.html
Any help would be great as I’d love to use this to set my backgrounds for my various pages but I’d also like a way to set up a gallery to navaigate from image to image in the same way George requested.
Thanks!
Michael Bester says:
140 days ago ∞
@Dave – The script requires jQuery in order to do its thing, and it’s missing from your test page. Be sure to include jQuery before this script.
By the way, you can either include this script inline as you’ve done, or you can put it in a separate file in order to take advantage of browser caching.
@Will – I haven’t seen that behavior in the PC browsers, but I’ll do some more investigating to see if I can recreate it.
Oleg says:
131 days ago ∞
Nice posts there – thank’s for the interesting information.
Private Myspace says:
131 days ago ∞
thank you for posting this
Gard says:
130 days ago ∞
Excellent posts, thank’s.
You're the Man...quick question says:
127 days ago ∞
Hey thanks for this…I’ve been pulling my hair out trying to figure this out. Now it is resizing in my browser but the bottom part of my 1280×960 background is not showing. It is showing about 80% of my background…any ideas on what I am doing? I copied your code word for word. Thanks for any help.
Michael Bester says:
127 days ago ∞
@You’re the Man – Do you have a URL I can look at? I can’t really tell you anything without seeing your implementation in context.
madesign says:
123 days ago ∞
Your solution do not work with Safari 4.0.3.
I am looking since a wile for a way to add a flexible BG image but it seams its difficult to find a solution which works with most and older browser like IE6
Saludos, Michael
Michael Bester says:
123 days ago ∞
@madesign – My example page works just fine in Safari 4.0.3 on OS X 10.6.1. I’ve also tested it successfully back to IE6.
Are you running Safari on Windows? And are you referring to my example or a site you’ve implemented the script on? If it’s your site, what’s the URL where you’re not seeing it work?
madesign says:
123 days ago ∞
Hello Michael,
i use Safari in Snow Leopard and both the context example and the working example do not work. I use a MacBookPro with screen 1440 px x 900 px.
Saludos, Michael
madesign says:
122 days ago ∞
Just realize you took the screens with Safari.
I can send you a screen if you wanted but both backgrounds are fixed image with a grey background left and right.
Michael
madesign says:
122 days ago ∞
Just test it also in Firefox 3.5.3 with the same result.
Only a static background image.
Michael
Mike Cutter says:
120 days ago ∞
Hi Michael, further info, I was initially getting a static bg image in Safari 4.0.3 (on 10.5.8 in my case) for both your example page and the live site. I then loaded the javascript file to have a look (for the example page), and then went back to the page (literally just hit the back button), and it’s suddenly working, including subsequent reloads. ???
Lorissa says:
119 days ago ∞
Thank you for this! It was just what I needed and worked like a charm. Excellent stuff.
seb says:
119 days ago ∞
Perhaps a little bit of a long shot, but I needed to fix a similar issue in a webpage I was recently creating.
Unfortunately I believe I can’t use your solution since what I wanted to do was to be able to scroll multiple background images in the background, similar to what is done on www.webtalents.pl
When I set an img to fixed or absolute, then it no longer can scroll, and I need to set them to that in order to use height/width…
I explained the issue better here: http://stackoverflow.com/questions/1554899/resize-background-image-to-fill-the-page-height-wise-but-allow-elements-on-eithe
Any ideas? It looks like I’m going to need to wait for various CSS3 features, and in the meantime just use a selection of pre-resized images. Very annoying!
Dave Wilson says:
117 days ago ∞
Hey Michael, thanks for that! I’ve got it working now and it’s pretty sweet. I do seem to have a similar issue to what you’re the man is having but I beleive it’s just a resolution issue. I have my image set to 640×480 so that elements on my background will hopefully work better during resizing. When my browser is maximized a good chunk of the bottom of the back ground is below the bottom of the browser window. This is due to maintaining the aspect ratio of the image. Again, not being very code headed, how would I change it up if I DID want 100% fill on height and width regardless of distorting my image so that the entire image is always visible but filling the screen?
Thanks Michael.
Adam Hays says:
109 days ago ∞
testing this in IE but doesnt seem to be working? website is longshots-bar.com
Sergey says:
98 days ago ∞
good code.
Dan McCreedy says:
89 days ago ∞
Thank you for taking the time to share your excellent work. I am trying to catch up from the HTML days of 2001. I have been imagining this concept in my mind yet do not have the proper training. This information has opened the door for me.
mindesign says:
84 days ago ∞
Currently building my website and needed this EXACT example solution.
Been in publication field for last 5 years and attempting to grasp the web media field as a recent layoff has demanded it.
I an truly grateful for your charity and best intentions.
Michael says:
54 days ago ∞
Great post..thanks a lot! I’d like to have the background image be randomized on refresh of the page. You mentioned in a previous comment that one could randomize the background-image property on the body element in the CSS… could you explain this a bit further? Thanks!
Michael says:
52 days ago ∞
Nevermind…I figured it out. You can see it at http://www.slavagoh.net
jorre says:
36 days ago ∞
For some reason I keep getting the following error:
Error: $body.css(“background-image”) is undefined
My background-image is set correctly as it displays on screen, I have no clue why this isn’t working. I’m using the latest jquery version.
Any ideas?
jorre says:
36 days ago ∞
the script needs to be at the bottom of the page… sorry for that ! Great script!
Ashur says:
27 days ago ∞
Hi,
This is by far the best solution that I’ve stumbled on, when it comes to flexi backgrounds. Could it be possible to add resolution detection to the code, so that e.g for a user with a resolution of 1024×768 > the script would offer an optional image? (Yes, I know the idea of the script is that the bg scales down, but I have an image that does not show as planned, when scaled down too much.) I would be greatfull for an answer.
Ashur says:
27 days ago ∞
This is by far the best solution I have stubled on when dealing with flexi backgrounds! I was just wondering that could it be possible to edit the code so that the script would seek up the user’s resolution and offer an optional image depending on screen width?(E.g if the user works with 1024×768, he would be given a diffrent bg than other users). I know the idea of the script is to scale, but I have a bg image that doesn’t work when scaled down too much (to a 1024 res.). Hope you could help..
Michael Bester says:
27 days ago ∞
The script does seek the user’s resolution, and compares it to the image dimensions you set in the
bgImageSizevariable and doesn’t scale the image any smaller than the dimensions you set there. Additionally, if the screen size is equal to or smaller than those dimensions, the script exits and does nothing at all.To actually show a different background image to those with a smaller monitor, I’d take another approach: I’d use Javascript to check the screen size and dynamically set a class name (say,
lowres) on the<html>element. That way, you can use 2 different CSS definitions , like so:body { background: url(image_1.jpg); }.lowres body { background: url(image_2.jpg); }You can then layer this script seamlessly on top of that functionality.
Dan says:
12 days ago ∞
I have tried writing my own script to do this and it always seemed to distort the image, well done.
Moreno says:
6 hours ago ∞
Awesome code! Thank you SO much.
How’d you get the content area transparent? Did you float the white background divs around it or?
Your Turn