The Flexible Scalable Background Image, Redux
29 Oct 10
Some time ago, I wrote about a method for setting up background images which scale to fill a browser window, regardless of the window’s size. Those background images would retain their aspect ratio as they scaled. They also wouldn’t shrink smaller than a certain size, as defined by the site author. By in large, the method was successful in meeting its goals, and it proved to be one of the most consistently trafficked posts I’ve written.
It was, however, a hacked together solution which leaned on Javascript to do the heavy lifting—a solution I was never competely satisfied with it. It didn’t work everywhere people wanted it to work (ahem, iOS devices), and it wasn’t as elegant as it could be. I eventually did some further research on the subject, and realzed that the latest crop of browsers could do it a better way. In modern browsers, the same result can be acheived using pure CSS. This ability is thanks to the fantastic CSS3 background-size property. By utilizing that property, along with a few simple media queries, we get the result we’re looking for.
The requrements for this version of the Flexible Scalable Background remain the same as those I set forth in the previous version. They are:
- 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.
We can add a few new requrements, too:
- Work in as many browsers as possible, including modern mobile browsers
- Implement as much as possible using only CSS.
So let’s get to it.
It’s all in the Styles.
In the original post, I stated, rather shortsightedly:
There was no way CSS was going to do it alone, even if I used CSS3’s
mythicalmarginally supportedbackground-sizeproperty.
That’s simply not the case any more. background-size is supported, in one form or another, in Firefox 3.6+, IE9 beta, Chrome 5+, Safari 4+, both in the desktop and in the mobile versions (the mobile implementation is currently less complete, but more on that in a bit). With such widespread support in the newest generation of browsers, it’s a good time to start utilizing the capabilities they have to offer. For these browsers, all the CSS that is necessary is as follows:
body {background-attachment: fixed;background-color: #333;background-image: url(/img/hanging-on.jpg);background-position: top center;background-repeat: no-repeat;margin:0;padding: 0;background-size: cover;-moz-background-size: cover;-webkit-background-size: cover;}/*This next definition doesn't allow the background to get any smallerthan a predefined size (640x426px in this case). Change the valueshere to match your background image size. The configuration in theflexi-background javascript file should also match these values.*/@media only all and (max-width: 640px) and (max-height: 426px) {body {background-size: 640px 426px;-moz-background-size: 640px 426px;-webkit-background-size: 640px 426px;}}/*The next 2 definitions are for support in iOS devices.Since they don't recoginze the 'cover' keyword for background-sizewe need to simulate it with percentages and orientation*/@media only screen and (orientation: portrait) and (device-width: 320px), (device-width: 768px) {body {-webkit-background-size: auto 100%;}}@media only screen and (orientation: landscape) and (device-width: 320px), (device-width: 768px) {body {-webkit-background-size: 100% auto;}}
Now there are a few things to point out here. First, background-size: cover simply tells the browser “scale the background image to fit the window without distorting the image’s aspect ratio”. That right there gets us most of what we are after here. One thing it doesn’t do, though, is set a minimium size below which the image will not scale. That’s handled in the second definition, where we specifiy the background-size in pixel dimesions. By wrapping that definition with a media query which specifies max-height and max-width values, we ensure the definition only gets applied when the window is smaller than those dimensions.
As of this writing, I found that not all browsers recognize the cover keyword for background-size, and as a result, simply ignore it. The most conspicuous of this group is Mobile Safari—the browser found on the iPhone, iPad and iPod Touch. We can overcome that limitation by setting the background-size to 100% width or height, depending on the orientation. We can target the current orientation (as well as the iOS device, using device-width) and apply the correct styles using media queries, as you see above.
Making it Work in the Less Capable Browsers
If we only wanted this functionality in the most modern browsers, we could stop right there. In the interest of playing nicely with the lesser browsers, such as IE8 and below, we have to add some additional functionality to mimic what background-size can do so well. Most of what follows is a repurposing of my original approach to this problem, which relies on JavaScript to fill in the blanks.
First thing we’ll have to do is add some additional CSS which works along with the JavaScript we’ll use. That looks like this:
img#expando {display: none;position: absolute;z-index: 1;-ms-interpolation-mode: bicubic;}.wide img#expando,.tall img#expando {display: block;}.wide img#expando {height: auto;width: 100%;}.tall img#expando {height: 100%;width: auto;}
Finally, we’ll have to add the JavaScript which makes it work. If you used the original flexibackground script, you’ll remember that the JavaScript relied on jQuery to do its thing. That always bothered me a bit, as I thought there wasn’t that much there that required the overhead that jQuery adds. In this version, I’ve stripped out that requirement and made the script completely self-contained. You may notice that there are some additional utility functions which take the place of what I had used jQuery for—thing like adding events and manipulating classes. They won’t interfere with anything in the event that you do have jQuery or some other library in your templates, but you no longer have to have it for this.
If you are using jQuery and you prefer the more concise code that we can write when utilizing that library, I’ve also included a version of the script which takes advantage of it in the project’s github repository. Just download the package from there and you’ll have either version to choose from. Just be sure to use either one or the other—not both together!
The standalone version of the script now looks like this:
(function(){/**CONFIGURATION:Define the size of our background image*/var bgImageSize = {width: 640,height: 426};/*END CONFIGURATION *//**Detect support for CSS background-size. No need for any more javascript if background-size is supported.Property detection adapted from the most excellent Modernizr <http://modernizr.com>*/if ((function(){var el = document.createElement('div'),bs = 'backgroundSize',ubs= bs.charAt(0).toUpperCase() + bs.substr(1),props= [bs, 'Webkit' + ubs, 'Moz' + ubs, 'O' + ubs];for ( var i in props ) {if ( el.style[props[i]] !== undefined ) {return true;}}return false;}())) {return;};/**We also want to leave IE6 and below out in the cold with this*/if ( false /*@cc_on || @_jscript_version < 5.7 @*/ ) {return;}/**If we've gotten here, we don't have background-size support,so we'll have to mimic it with Javascript.Let's set up some variables*/var elBody,imageID= 'expando',tallClass= 'tall',wideClass= 'wide',elBgImage, elWrapper, img, url, imgAR,/**Since we're not relying on a library, we'll need some utility functionsFirst, basic cross browser event adders*/addEvent = function(el, evnt, func) {if (el.addEventListener) {el.addEventListener(evnt, func, false);} else if (el.attachEvent) {return el.attachEvent("on" + evnt, func);} else {el['on' + evnt] = func;}},domLoaded = function(callback) {/* Internet Explorer *//*@cc_on@if (@_win32 || @_win64)document.write('<script id="ieScriptLoad" defer src="//:"><\/script>');document.getElementById('ieScriptLoad').onreadystatechange = function() {if (this.readyState == 'complete') {callback();}};@end @*//* Mozilla, Chrome, Opera */if (document.addEventListener) {document.addEventListener('DOMContentLoaded', callback, false);}/* Safari, iCab, Konqueror */if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) {var DOMLoadTimer = setInterval(function () {if (/loaded|complete/i.test(document.readyState)) {callback();clearInterval(DOMLoadTimer);}}, 10);}},/**Next, a way to properly get the computed style of an elementCourtesy of Robert Nyman - http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/*/getStyle = function(el, css){var strValue = "";if (document.defaultView && document.defaultView.getComputedStyle){strValue = document.defaultView.getComputedStyle(el, "").getPropertyValue(css);}else if (el.currentStyle){css = css.replace(/\-(\w)/g, function (strMatch, p1){return p1.toUpperCase();});strValue = el.currentStyle[css];}return strValue;},/**Finally, some element class manipulation functions*/classRegex = function(cls) {return new RegExp('(\\s|^)'+cls+'(\\s|$)');},hasClass = function(el, cls) {return el.className.match(classRegex(cls));},addClass = function(el, cls) {if ( ! hasClass(el, cls)) {el.className += ' ' + cls;}},removeClass = function(el, cls) {if (hasClass(el, cls)) {el.className = el.className.replace(classRegex(cls), '');}},/*Now we can move on with the core functionality of Flexibackground*/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;}// Grab elements we'll reference throughoutelBody= document.getElementsByTagName('body')[0];// Parse out the URL of the background image and drop out if we don't have oneurl = getStyle(elBody, 'backgroundImage').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 elementelBgImage = document.createElement('img');elBgImage.src = url;elBgImage.id = imageID;// Create a wrapper and append the image to it.// The wrapper ensures we don't get scrollbars.elWrapper = document.createElement('div');elWrapper.style.overflow= 'hidden';elWrapper.style.width= '100%';elWrapper.style.height= '100%';elWrapper.style.zIndex= '-1';elWrapper.appendChild(elBgImage);elBody.appendChild(elWrapper);// Fix the wrapper into positionelWrapper.style.position= 'fixed';elWrapper.style.top= 0;elWrapper.style.left= 0;// Set up a resize listener to add/remove classes from the bodyaddEvent(window, 'resize', resizeAction);// Set it up by triggering a resizeresizeAction();},/**Set up the action that happens on resize*/resizeAction = function() {var win = {height: window.innerHeight || document.documentElement.clientHeight,width: window.innerWidth || document.documentElement.clientWidth},// The current aspect ratio of the windowwinAR = 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) {removeClass(elBody, wideClass);removeClass(elBody, tallClass);} else if (winAR < imgAR) {removeClass(elBody, wideClass);addClass(elBody, tallClass);// Center the imageelBgImage.style.left = Math.min(((win.width - bgImageSize.width) / 2), 0);} else if (winAR > imgAR) {addClass(elBody, wideClass);removeClass(elBody, tallClass);elBgImage.style.left = 0;}};// When the document is ready, run this thing.domLoaded(initialize);})();
I should point out that I’ve dropped support of IE6. It was always a bit fiddly to get it working correctly, and even then it was never great. Everything works as you expect it to in IE7 and above, and pretty much any other browser I’ve thrown at it.
Finally
Try out a working example of this new version in any browser you have handy. Go ahead and download it for your own use from Github. I’d be glad to hear what you think and I’d love to see what you do with it.








Comments
Brad Touesnard says:
30 October 2010 ∞
Great post! I will be using it as a guide in an upcoming project. Kind of annoying you didn’t use jQuery this time around though. The script would be a lot more concise.
Disco Liam says:
1 November 2010 ∞
Excelent work again. We where planning on using the older methond on a upcoming project, but Ill update it to this one. Much like Brad I would of prefered it in jQuery since we will be using the libary anyway. I might have a go at converting it to the query.
Phlub says:
1 November 2010 ∞
Doesn’t seem to be working in Safari (WebKit)… anyway to resolve that? Works great otherwise!
Phlub says:
1 November 2010 ∞
Works w/ Safari 5+… my bad. (not 4)
Thanks again!
Brad Touesnard says:
2 November 2010 ∞
Awesome, thanks for the jQuery update Mike.
heath waller says:
4 November 2010 ∞
I’m thrilled you’ve updated this tutorial. I used your old technique for a project in the past – and it worked like a charm (this is after trying about half a dozen other solutions). I can’t wait to use this new one on my new portfolio site.
Thank you for sharing your wonderful work with everyone :)
Michael Jung says:
4 November 2010 ∞
Hi Michael
Thanks for this solution – I like the jQuery-free version.
More stable in my environment – BUT – one word about IE6 ;-)
I think it would be really nice if you could force IE6 not to show a (any) background-image – just the background-color defined in css. Now the picture is shown twice.
perhaps you can help me.
thanks for sharing this
Michael
Jarno 51 says:
9 November 2010 ∞
Hi Micheal, great thing you have here!
Sadly enough, I experienced the thing you mentioned in you last post.
1 thing, on this version the adaptation of the “body” isn’t done….The script for IE 8 does not work if you haven’t defined a body background-image.
The thing is, I managed to get it to work for background images on regular divs in compliant browsers (FF & Chrome)
But in IE8 I failed. I tried to change the javascript line :
elBody= document.getElementsByTagName(‘body’)0; by replacing the ‘body’ by my div id, but it didn’t work.
Is there a solution to this problem?
Because I would want to use it on a project which have mutliple bg. in mutliple Divs
you can see it working in an compliant browser here :
http://www.grafical.be/Lacouleur-test5/
Thank you for the help!
Michael says:
10 November 2010 ∞
Hello. Firstly, I love the design you’ve got going here – gorgeous stuff. Is this a custom font? I don’t recognize it.
Secondly, I was wondering if you could help me. I’m using this awesome code in my html for my blog, but it doesn’t seem to be working in IE8. Am I perhaps doing something wrong?
Any response will be greatly appreciated.
Pascal Brun says:
12 November 2010 ∞
Yes, yes, yes! Great. .the update for iOS I been waiting for.. thanks for sharing!
Jonathan Nicol says:
17 November 2010 ∞
You, my good Sir, just saved me a lot of hair pulling. Awesome work.
Michael says:
17 November 2010 ∞
Seriously, what the deal is?
Everything works in Firefox but not IE, please help if you can.
Thanks very much
Michael Waskosky says:
24 November 2010 ∞
I’ve been enjoying your script and technique, and had marginally addressed IE8 by using a simplistic HTML IE comment workaround that I placed after everything <!—[if IE]>
<style type=”“text/css”>
body { background-position:center top; background-repeat: repeat-y; background-attachment:none;
}
</style>
<![endif]—> (I also turned off the JS for <=IE8)
…however I was still unsettled by the fact that FF has poor performance when using “fixed” backgrounds. I found this very recent article today http://css-tricks.com/perfect-full-page-background-image/ which covers a lot of methods, giving specific browser compatability information for each, and settled on “CSS technique #1” for IE8 compatibility purposes, although FF is still slow for me when scrolling but I’m crossing my fingers that it will get better with future versions.
I was also persuaded to switch to this foreground image solution because I just read that IE9 will not be coming to windows XP users, who still account for half of the people out there, and so it seems like we are probably going to be stuck with IE8 compatibility concerns for a long time http://www.w3schools.com/browsers/browsers_os.asp (assuming you want to catch 90%+ of users)
Lya says:
27 November 2010 ∞
hi, wonder stuff, thank you for sharing…curiously i was wondering if there was a possible way to resize the background of a table image, either this way or like your previous post. thanks ^^
Kim says:
4 December 2010 ∞
This is brilliant work and I have used it for a website which is just about to launch. Unfortunately when I tried the site on an ipad it didn’t quite work. The image fits the screen, but when I scroll the background goes too. I can’t tell if that happens with your example as the content isn’t big enough to scroll. I’ve read elsewhere that mobile safari doesn’t support background-attachment:fixed and you need JS to make it happen.
Have I screwed something up, or is this how you would expect it to be?
Thanks
Lillie says:
7 December 2010 ∞
This is exactly what I have been looking for, although when it comes to scripting and coding, I am somewhat of a novice. I am building a WordPress site using the new TwentyTen theme which allows for a custom background feature. I have no idea where I would add this script. I am thinking it would go in style.css and I would just not use the custom feature? Any help is greatly appreciated!
Ed says:
31 December 2010 ∞
I found a nice example of a flexible background image here: http://www.setupsigning.nl/ Can anyone tell me how this behaviour was created?? I’m a bit of a novice, but the script of this thread doesn’t seem to work for me for some reason..
Michael says:
15 January 2011 ∞
This doesn’t seem to work for me in IE7 or IE8… I have double-checked everything and code/styles look ok. Can you help me, please?
PiT.79 says:
16 January 2011 ∞
Hello, can anybody help me? I try to combine this great flexible scalable background image function with picture slideshow based on protofade (http://cssrevolt.com/upload/files/protofade/). I need to flexible scalable images inside DIV tag with specific ID referred in protofade slideshow. Is it possible or how can i achieve this effect? I need to – Picture slideshow with smooth transitions on the backgroun of the page with some div elements infront of it (menus, text description, contact, etc.), picture slideshow should full filled window in scale, centered horizontaly, verticaly sitting on the bottom edge of window, and chance to control the slideshow (stop, play, next, back) via menu. Do you have any solution? Click my nickname to get on discussed site. Thanks for help.
Tumikia says:
26 January 2011 ∞
This worked perfectly for me and is exactly what I was looking for except I can’t scroll to see the rest of my content. Is there a way to remedy this situation. I am trying it out at www.jenumijewelry.com
Help, please.
Derek Montgomery says:
5 February 2011 ∞
I was trying to figure out how to do this for a client’s website and this was by the far the easiest and quickest solution. You made my day and it’s only 9:26am where I’m at.
Cole says:
7 February 2011 ∞
this is a great post. was able to get the image background working, but is there anyway to replace the image with .swf file or movie?
similar to what is happening here?
http://interactive.nfb.ca/#/pinepoint
thanks!
Cole says:
7 February 2011 ∞
is there a solution for randomized image on refresh for the updated code?
md says:
9 February 2011 ∞
bg image doesnt scale correctly on ios.
Mike says:
18 February 2011 ∞
For inspiration, there’s also an article that lists various websites and alternate techniques when designing with scalable background images. http://www.noupe.com/design/scalable-full-body-backgrounds-in-web-design.html
Vincent Drolet says:
22 March 2011 ∞
Thanks a lot for this piece of code, I had problem with iPhone compatibility and now it’s working perfectly. If you guys need an example with manual and random background rotation here it is: labandeapaul.com.
Lore says:
1 July 2011 ∞
Thank you very much for this great piece of code.
John says:
22 July 2011 ∞
Can you shed some light on how we might call this function after the page has already loaded. I have other javascript functions that change the background image and I need to be able to reinitialize the plugin at will.
I’ve looked up jQuery documentation, and tried editing the source myself several times, but just can’t seem to get it.
John says:
22 July 2011 ∞
Nevermind, I was able to figure out what I needed from Vincent Drolet’s example. Great technique!
Michael says:
12 October 2011 ∞
Thanks so much for that. What a treat. Check out what I did with it. Warm orangey goodness. Thanks again.
Trisa says:
30 October 2011 ∞
That is a lot of code but I guess if you want it to render right you gotta have it. Thanks
Kasia says:
21 November 2011 ∞
Thank you!
Dunkan dieet says:
23 November 2011 ∞
Thank you for share the iOS code with us. I can use it very well.
Dcoomer says:
30 November 2011 ∞
Could someone please help explain where i should place the javascript? ive imported the css stuff into my template css , im useing Joomla as a CMS and dont know where i should import the javascript portion? Any help woul dbe greatly appreciated!
css only says:
20 January 2012 ∞
great stuff, but don’t see the point of the javascript. should be doable with css only.
Timothy says:
6 March 2012 ∞
vincent drolet /john_i am completely stumped looking @vincent’s website.
Everything else is great, but i cannot figure out evolving this script to incorporate a random background rotation.
any pointers?
Thanks!
Eetu says:
7 March 2012 ∞
Thank you very much – this really helped me alot with my site project!
Ryan Calderon says:
7 March 2012 ∞
What a great plugin!! However I have a little concern and will be very grateful if you could help me.
Is it possible to use that in <img> tag inside HTML instead of using class in the body?
Many thanks.
Vibeke says:
21 March 2012 ∞
Thank you for a great article/code! It works perfectly in all browsers on the two sites I have used the code on. However, it does not work as it should on iPad… the large background doesn’t cover the whole screen – it shows a 1,5 cm white stripe at the bottom in both portrait and landscape orientation. The image is originally 1950px x 1080px. Any tips?
Michael Bay says:
13 April 2012 ∞
Hello.. What a nice script, but how do i get scrollbars? i want this script, just with the side scroll bars
Thanks
Michael Bay says:
13 April 2012 ∞
Cool script. But i need the scrollbar from the side. How to do that?
Regards, Michael
Zoller says:
11 May 2012 ∞
Thanks for this amazing solution ^^
But i still have a little problem with it. On every browser, my background needs to be aligned to left, but doesn’t matter if i change the basic css declaration for body, its keeping on centered :( The second problem is what the background-image is oversized by default :S I only want it to be at the default size when is use 1920*1080px wide resolution, and shrink when i use smaller browser-window.
Can anyone help me? Very important to figure it out ^^
Harris Sharpe says:
28 May 2012 ∞
If only you spoke in English :p
BSmith says:
24 July 2012 ∞
worked like a charm, Thank you.
Mihai says:
27 August 2012 ∞
THANK YOU SO MUCH
Fabio says:
13 December 2012 ∞
Guys, am I the only one that cannot making it working on IE?? I am trying all techniques I’m finding on internet but I have no luck at all. I don’t know if it could be the image in this case. I created the image 1900 × 1080 to have the maximum size possible and then trying to resize it on smaller screens but on IE I get no luck at all.
I have a testing version here: http://www.mondopallone.it/staging
Bespoke Software Development says:
15 April 2013 ∞
Perfect! Works like a dream. Saved me a lot of hassle. Thank you!
Your Turn