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!




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