In recent thread over at the SitePoint forums, someone asked how to have any sized image fit nicely into the viewport while maintaining its aspect ratio. The assumption is that all the content can fit in the viewport and no scrollbars are needed. While CSS is wonderful and magical, it doesn't know the sizes of images and let us play with them to pixel perfection, so we must turn to good old JavaScript to maintain aspect ratio.
This is a very large photo I took of a mountain near Choquequirao, Peru. Figure 1 shows it set as a normal background image without playing with CSS's background-position or anything else and only a rather boring corner of it (not that the whole image is that exciting either) is visible:
Figure 1
Figure 2 shows the same thing, except that the image has been centred using background-position:center:
Figure 2
It's even more boring! Now, with CSS we can still improve this a bit, by using the img element instead of a background image. Yes, this is not as beautifully semantic, but for the purposes of this example will do:
By setting the width and height of #bg and the image to 100%, we can fill the viewport up with the image, but of course the aspect ratio of the image will not be maintained and the stretchiness can look horrible, particularly if the browser window is resized.
Time for some JavaScript, then. The first thing to do is to make sure the image has actually loaded before we try to resize it. I've decided on the following approach:
-
var bgimg;
-
var init = function() {
-
bg = document.getElementById('bg');
-
bgimg = bg.firstChild.nextSibling;
-
bgimg.onload = resizeBg;
-
bgimg.src = bgimg.src;
-
if (window.addEventListener) window.addEventListener('resize', resizeBg, false);
-
else if (window.attachEvent) window.attachEvent('onresize', resizeBg);
-
}
The key thing here is that we first set an onload event listener on bgimg (the image we're resizing) which will fire when the image has finished loading. However, it will only fire if the src attribute is set, so we do that in the following line (it doesn't matter that it's the same src, fortunately).
The last three lines fire resizeBg when the browser window is resized and cater for decent browsers and IE. Here is resizeBg:
-
var resizeBg = function() {
-
var w = self.innerWidth || document.documentElement.clientWidth;
-
var h = self.innerHeight || document.documentElement.clientHeight;
-
if (bgimg.width !== w) {
-
bgimg.parentNode.style.left = '0';
-
bgimg.parentNode.style.top = '0';
-
bgimg.height = (w / bgimg.width) * bgimg.height;
-
bgimg.width = w;
-
}
-
if (bgimg.height <h) {
-
bgimg.width = (h / bgimg.height) * bgimg.width;
-
bgimg.height = h;
-
bgimg.parentNode.style.left = '-' + ((bgimg.width - w) / 2) + 'px';
-
}
-
else if (bgimg.height> h) {
-
bgimg.parentNode.style.top = '-' + ((bgimg.height - h) / 2) + 'px';
-
}
-
}
The first two lines obtain the width and height of the viewport. There is a small caveat here: For document.documentElement.clientWidth to work (since IE does not support self.innerX), IE must not be in quirks mode, i.e. the page must have a valid doctype. Not much of a caveat, really, since your pages should have valid doctypes anyway.
Following that, we just employ some simple mathematics. The first if block will set any image to the same width as the viewport. It also works out the new height for the image so that the aspect ratio is preserved.
The next if-else block is important. If the first block's calculations means that the new image's height is smaller than the viewport, we need to make it a bit bigger. This means setting the width to bigger than the viewport, so to preserve the aspect ratio, we centre it by shifting it left by half the difference.
If the height of the new image is bigger, then we need to centre it vertically, which is what the else if bit does.
Now, as shown in Figure 3, we can get a nicely-fitting centred image as the background!
Figure 3
See the finished example page (which Figure 3 links to too) and the accompanying JavaScript file. The JavaScript file includes code to run the resizing function when the DOM has loaded, using Dean Edwards' script, so the init function will look a little different to the one on this page.
Before anyone says anything, yes, the script could be made truly nice and unobtrusive by creating the HTML elements for the background image with JavaScript as well, but that is not the focus of this post, so it can be left as an exercise for the reader!
Update (29/01/09): I've updated the code to make the script work in IE6. Please note there are also changes to the CSS (in the finished example HTML file).




June 17th, 2008 at 9:40 pm
Nice one Raf, it’s funny how some things just never seem to come up until someone asks you for it. I have never even thought of doing that or seen anyone do it, but it makes sense that someone somewhere would need this.
June 18th, 2008 at 12:54 am
Please continue to post more hacks and workarounds like this…. I don’t currently have this specific problem, but i have in the past, and may in the future,so thank you!
June 19th, 2008 at 10:42 am
A good way when working with images is to preload them.
Though, this is a nice example!
Tip: Your script throws an error in IE7.
June 27th, 2008 at 7:17 am
Hi, just what I’m looking for, but I’m getting an error:
document.getElementById(“bg”) has no properties
init()background-image-… (line 5)
[Break on this error] bgimg = document.getElementById(‘bg’).firstChild.nextSibling;
Any ideas? I don’t know JS so apart from adjusting the image name to mine I haven’t changed any of the code.
June 27th, 2008 at 8:39 am
Oops – never mind: fixed it: bg is the name of the div. I don’t suppose there’s quick a fix to allow it to behave like background-attachment:fixed is there? :-/
July 10th, 2008 at 1:55 am
Hi Raf,
Nice article
I notice in IE6 that you have a massive horizontal scrollbar. Looks like the overflow needs to be hidden on #bg.
Otherwise good job, I look forward to more articles.
Paul
July 25th, 2008 at 3:44 am
Perhaps you can add an article about java applet, cause it will enchance the look of the image, like snow effects. I’ve ever visited a website with this kind of effect. But the problem is the site seems to be slow-loading. Don’t know whether there is a way to make it faster.
August 6th, 2008 at 3:01 pm
Hi Raffles,
Clever script. I’m trying to get it to work in IE, but to no avail.
The image simply won’t resize, but rather, sits top left at full size. Does anyone have any workarounds for IE?
Thanks
August 6th, 2008 at 3:06 pm
P.S. The issue seems to be with the “onreadystate” declarations on lines 37 and 38.
The error message I receive when trying to view the page in IE is as follows:
Line: 39
Char: 5
Error: ‘bgimg.readyState’ is null or not an object
Code: 0
URL: http://www.search-this.com/wp-content/uploads/2008/06/fsbg.html
August 8th, 2008 at 2:00 am
Hi all

i m new to this pages
im interesting in this great script to fit background
i have the same IE6 / 7 problems… on line 38/39 (readyState)!
i ve written down a correct code but… no reason!!!
…
the code (from Firefox to Explorer lines):
var alreadyrunflag=0; //flag to indicate whether target function has already been run
if (document.addEventListener)
document.addEventListener(“DOMContentLoaded”, function(){alreadyrunflag=1; init();}, false)
else if (document.all && !window.opera){
document.write(”)
var contentloadtag=document.getElementById(“contentloadtag”)
contentloadtag.onreadystatechange=function(){
if (bgimg.readyState==”complete”){
alreadyrunflag=1;
init();
}
}
}
window.onload=function(){
setTimeout(“if (!alreadyrunflag){init();}”, 0)
}
any reason ?
yo!
Roby
January 28th, 2009 at 1:12 am
Has anyone gotten this to work in ie 6/7 and firefox 2?
Can’t seem to figure it out.
January 29th, 2009 at 4:08 pm
There was a mistake in the code that stopped it working in IE6, should be all OK now.
January 30th, 2009 at 1:53 am
Hey Raffles
a mistake in the CODE?!
but… have you modify some code lines?!
and now?! It run correctly both in Firefox and in IE 6/7 ?
yo!
February 1st, 2009 at 7:47 am
Yes, in the JavaScript. The files have been updated, so just look at the sample HTML and JS files for the code. I’ve tested in FF2/3 and IE6/7 and they work.
February 1st, 2009 at 11:25 am
Very very good


Thankssssssssss Raffles
Now run cross-browser
Yo!
March 2nd, 2009 at 8:52 pm
this works great, but have one question.
on a mac, you can see that it loads the whole image first before it resizes it. in other words, you see the image at full scale, then it immediately scales down to the browser.
is it possible to resize the image before you see anything so that you don’t see that jumping in scale of the image?
thanks!
March 2nd, 2009 at 8:57 pm
update:
the jumping in scale happens in all OSs and browsers, not just mac. thanks.
June 12th, 2009 at 3:18 am
Thank you Mr. Raffles!
You’d think that stretching an image to make best use of available space would be a really common requirement, well served with examples on just about every decent coding site. Not so! Yours is the only one I’ve found that actually does what I need.
August 21st, 2009 at 2:20 am
Hey Raffles, this is an awesome thing. It works like a charm, and with a bit of tweaking from my end, now it also looks graphically very well.
However.. I have one question, being: is it possible to make the script more dynamic? The reason is that I would like to use this as a base of an image gallery, and when a new image is clicked, the background changes. Do you reckon this is possible??
Cheers, thanks for the great script Raffles.
December 10th, 2009 at 9:54 am
Hi there. This looks like an amazing bit of script, but one thing is stumping me and that is how to get long content to scroll. Do you have any possible solutions for this or am I just missing the obvious perhaps?
Any help would be greatly appreciated!
December 13th, 2009 at 6:56 am
@ booyah:
The jumping in scale happens because bgimg.src is set before the resizing happens. If you want to hide it beforehand, then you simply have to toggle the display property:
#bg img {display:none}
Then add this right at the end of the resizeBg function:
bgimg.style.display = ‘block’;
@ Christiaan:
Yes, it would certainly be easy enough to do. All you would have to do whenever you want the image to change is:
1. Make sure the image is preloaded
2. Hide the image
3. Change the src attribute
4. Run the resizeBg function
5. Show the image
It might require a second holder DIV, as this procedure might cause a nasty flash of emptiness.
You could even add a nice fading transition between the two images.
@ Brad
I hadn’t thought of that. The solution is to do the following:
#bg {position:fixed}
December 14th, 2009 at 1:57 am
Thanks for that Raffles. Yes, this worked, but you also need to change overflow from hidden to visible on the html, body.
Amazingly enough this script seems to dodge the Firefox scrolling bug over fixed background images too.
Cheers.
May 17th, 2010 at 1:09 pm
Hello,
sometimes I have to reload the page before the image is displayed correctly. Is this a know issue= How can I solve this problem?