June 17th, 2008 - by Golgotha

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:

Huge image, now centred

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:

[HTML]

Main content, overlapping background image

[/HTML]

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:

[JavaScript]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);
}[/JavaScript]

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:

[JavaScript]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’;
}
}[/JavaScript]

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!

Full-screen background images thumb

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 Lose Weight 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).

23 Responses to “Nicely-Fitting Background Images”

1 Golgotha

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.

2 Link Building Bible

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!

3 Costin Trifan

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. 😉

4 Martin Thompson

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.

5 Martin Thompson

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? :-/

6 Paul OB

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

7 Yanto

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.

8 Jared

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

9 Jared

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

10 king27

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

11 booyah

Has anyone gotten this to work in ie 6/7 and firefox 2?
Can’t seem to figure it out.

12 Raffles

There was a mistake in the code that stopped it working in IE6, should be all OK now.

13 king27

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!

14 Raffles

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.

15 king27

Very very good 🙂
Thankssssssssss Raffles 😀
Now run cross-browser 😉
Yo!

16 booyah

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!

17 booyah

update:
the jumping in scale happens in all OSs and browsers, not just mac. thanks.

18 GammyKnee

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.

19 Christiaan

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.

20 Brad

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!

21 Raffles

@ 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}

22 Brad Shaw

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.

23 Alex

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?

mulberry sale spyder womens jacket cheap new balance 574 mulberry outlet cheap new balance 574 arcteryx outlet mulberry sale spyder womens jacket mulberry sale spyder womens jacket mulberry outlet mulberry outlet new balance 574

Popular Articles

Top 10 Commentators


Subscribe to this feed! Subscribe by Email!

Random Bits Podcast

You need to download the Flash player from Adobe

Blogs Worth Reading