Perfect Popups

Navigation

Skip navigation.

Site search

Site navigation

Script details

Popups that resize to fit their contents

This describes how to open a window, dynamically write its contents, then resize it to exactly fit those contents. This technique could also be used to make a window resize to fit its contents, even if it has not been dynamically created.

Don't abuse popups, you will end up loosing visitors who become annoyed by them. This technique is perfect for shopping cart product lists or photo galleries, where clicking a link opens up a popup containing just a picture.

Note, if you just want to resize and reposition the current window so it perfectly matches your screen, you want this:

window.moveTo(0,0);
window.resizeTo(screen.availWidth,screen.availHeight);

Or for a popup:

var foo = window.open('somepage.html','_blank','someOptions');
foo.moveTo(0,0);
foo.resizeTo(screen.availWidth,screen.availHeight);

If you want something smarter, read on.

To see the script license and check details like browser compatibility, use the links on the navigation panel at the top of this page.

Opening a window and dynamically writing its contents

See my customisable window page for how to open windows, as well as my writing with script page for how to dynamically write contents.

var x = window.open('','windowName','width=500,height=400,resizable=1');
if( !x ) { return; } //browser is blocking popups

The url '' is special, it means we can write the contents without having to wait for a page to load. 'resizable' must be 1 or some browsers will not allow the auto-resize to work correctly. Initial height/width are not important.

x.document.open();
x.document.write('contents');
x.document.close();

The contents should contain the full HTML required for the popup page.

Resizing the window to fit its contents

Many browsers provide a way to find the size that the page is using. The problem is that they all require different ways to do this. To avoid this, I put the contents into a positioned element, positioned at 0,0, with the width set to the desired width. I then find the size of the positioned element and resize the window to that size. Remember that when writing positioned elements in Netscape 4, you need to use the layer tag, not a div. That is probably not important to your site, but this script will take care of it, just in case.

A major stumbling block here is that resizeTo resizes the outside of the window, not the inside (except in some Netscape 4 versions), and I need to resize to fit the contents, so I need to resize the inside. Although some Netscape compatible browsers allow me to write to window.innerWidth/Height, not all do, so I will need to use resizeTo. How? Well, what I do first is to assume that the total size of the menus, toolbars, window frames etc. is less than 200px high and wide. I then resize to the desired size, plus 200 pixels in both directions. Why? So that no scrollbars accidentally appear. Otherwise, the size I calculate would add in the size of scrollbars, when the finished window should not have any.

Note: I could have also done this; found the current size of the inside of the window, found the size of the content, then used window.resizeBy() to resize by the required amount. I choose not to do this, because if the window was initially smaller than the contents, the script would not take scrollbars into account, and we would be left with gaps in the final window. Since different browsers and skins use different sized scrollbars, I need to take my measurements while there are no scrollbars showing. Instead, I use resizeTo first to make sure there are no scrollbars, then I take the measurements before resizing again. Using resizeTo the way I do means that I do not have to measure the window size twice, saving a significant amount of extra code.

I now know how big I asked the window to be, and I check how big it made the inside of the window (see my window size page for more information). I work out the difference, then add that on to the size I wanted to make the inside. I then resize again to the new calculated size, and the window resizes so that the inside exactly matches the size I originally wanted. This may seem long winded, but it is in fact very fast, and works in virtually all modern browsers.

Note: I could have also have used window.resizeBy() for the final resize, but that would not save me any code, and resizing the outside is actually more useful for things like positioning the popup in the centre of the screen.

Getting the size of the positioned element

You may already know this if it contains just an image etc., so you could skip this part. Also, you already know the width, as you set it earlier, but if needed, this technique can also be used to calculate the width. This uses the function I created in the DHTML tutorial section, subsection referencing the positioned element. I have modified the function to look in the document object of the popup, not the main window. If the browser cannot reference the div, or if it cannot calculate its size, there is nothing more I can do. Just stop. If you know what size the contents are, for example, if the content is an image, and you just want to frame the image, you can skip this part, but you will need to define the variable oH yourself. I pass oW to the function, you can do the same with oH if you want.

var oH = getRefToDiv( 'theIDOfTheLayerOrDiv', x.document ); if( !oH ) { return; }
//calculate oW here if you need to: var oW = oH.clip ? oH.clip.width : oH.offsetWidth;
var oH = oH.clip ? oH.clip.height : oH.offsetHeight;
if( !oH ) { return; }

Clip is used because layers browsers provide clip.width/height, even if clipping is not used. Non layers browsers use the offset properties.

Note that due to a bug in the quirks mode handling in Mozilla/Firefox, the reported width of the positioned element will be wrong if the content is too wide for the screen. This is a particular problem for image popups (see below). To avoid this bug, the display:table; style should be used to force the div to stretch to fit its contents. This is the desired behaviour, and causes the correct response in browsers that support it (Opera, Mozilla/Firefox, Safari, etc). Browsers that do not support it get it correct anyway (Internet Explorer 7-).

Resizing the window

See my window size page for more information.

x.resizeTo( oW + 200, oH + 200 );
var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;
if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }
else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }
else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }
//Opera 6- adds on 16 pixels for the non-existent scrollbar
if( window.opera && !document.childNodes ) { myW += 16; }

Now that I have the size of the inside, I subtract it from the size we asked, then add that onto the desired size.

x.resizeTo( oW + ( ( oW + 200 ) - myW ), oH + ( (oH + 200 ) - myH ) );

Finally, I focus the window, just in case it had been used before and was not in the foreground.

if( x.focus ) { x.focus(); }

The finished product

function getRefToDivMod( divID, oDoc ) {
  if( !oDoc ) { oDoc = document; }
  if( document.layers ) {
    if( oDoc.layers[divID] ) { return oDoc.layers[divID]; } else {
      for( var x = 0, y; !y && x < oDoc.layers.length; x++ ) {
        y = getRefToDivMod(divID,oDoc.layers[x].document); }
      return y; } }
  if( document.getElementById ) { return oDoc.getElementById(divID); }
  if( document.all ) { return oDoc.all[divID]; }
  return document[divID];
}

function openPerfectPopup(oW,oTitle,oContent) {
  var x = window.open('','windowName','width=500,height=400,resizable=1');
  if( !x ) { return true; }
  x.document.open();
  x.document.write('<html><head><title>'+oTitle+'<\/title><\/head><body>'+
    (document.layers?('<layer left="0" top="0" width="'+oW+'" id="myID">')
      :('<div style="position:absolute;left:0px;top:0px;display:table;width:'+oW+'px;" '+
      'id="myID">'))+
    oContent+(document.layers?'<\/layer>':'<\/div>')+'<\/body><\/html>');
  x.document.close();
  var oH = getRefToDivMod( 'myID', x.document ); if( !oH ) { return false; }
  var oH = oH.clip ? oH.clip.height : oH.offsetHeight; if( !oH ) { return false; }
  x.resizeTo( oW + 200, oH + 200 );
  var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;
  if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }
  else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }
  else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }
  if( window.opera && !document.childNodes ) { myW += 16; }
  x.resizeTo( oW + ( ( oW + 200 ) - myW ), oH + ( (oH + 200 ) - myH ) );
  if( x.focus ) { x.focus(); }
  return false;
}

The HTML contains:

<a href="noPop.html" onclick="return openPerfectPopup(300,'Title','Contents');">Open a popup containing text</a>.

Test it here: Open a popup containing text.

A special note for Firefox: Some Firefox releases have trouble deciding when to show the status bar. As a result, if there is not enough text in the popup window (as shown in the example above), they may leave a gap underneath the text, caused by the non-existent status bar that appeared during the resize. Adding a couple of extra lines of text into the popup above would avoid this problem. (Thanks to Peter Schroeder for reporting this bug.)

A special note for Chrome: Google's Chrome made several experiments with the UI, and one of the experiments was with the way it handles popups. These experiments made it fail to handle resizing correctly, and as a result, it does not work correctly with this script. The popup windows usually end up too big. This is a bug in Chrome, and I do not intend to work around it. Hopefully, the Chrome development team will at some point fix the bug.

But what about centering the popup on the screen (you may notice that I did that too)?

Well, this is also quite easy. Since the last time we resize the window, we know what size we are resizing it to, and therefore we know how big the outside of the window is. We can also measure the size of the screen (I like to find the available size just in case). Therefore, all we have to do is to reposition the centre of the popup in the centre of the available screen space. Simply replace this:

  x.resizeTo( oW + ( ( oW + 200 ) - myW ), oH + ( (oH + 200 ) - myH ) );

With this:

  x.resizeTo( oW = oW + ( ( oW + 200 ) - myW ), oH = oH + ( (oH + 200 ) - myH ) );
  var scW = screen.availWidth ? screen.availWidth : screen.width;
  var scH = screen.availHeight ? screen.availHeight : screen.height;
  x.moveTo(Math.round((scW-oW)/2),Math.round((scH-oH)/2));

If Opera users are using the MDI interface, it will be slightly offset (depending on the toolbar positions), since with Opera's MDI, the 'avail' measurements and 'moveTo' do not work in conjunction with each other.

Resizing the inside of the current window

If the window is already open, and you want to resize it to fit its contents, the process is the same as before, without the opening and writing parts. Many browsers do not allow resizing if the window is maximised. Again, I recommend using an absolutely positioned DIV. The window must not be resized until the page has completed loading:

function resizeWinTo( idOfDiv ) {
  var oH = getRefToDivMod( idOfDiv ); if( !oH ) { return false; }
  var oW = oH.clip ? oH.clip.width : oH.offsetWidth;
  var oH = oH.clip ? oH.clip.height : oH.offsetHeight; if( !oH ) { return false; }
  var x = window; x.resizeTo( oW + 200, oH + 200 );
  var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;
  if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }
  else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }
  else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }
  if( window.opera && !document.childNodes ) { myW += 16; }
  x.resizeTo( oW + ( ( oW + 200 ) - myW ), oH + ( (oH + 200 ) - myH ) );
}

Note that you also need the getRefToDivMod function from the example above (see the section called "The finished product"). I recommend that you only use this technique in a page that was opened in a popup window.

Resizing a popup to perfectly fit an image, even if you do not know how big that image is

This is an adaptation of the script that opens a window containing an image (even falls back to regular windows if popups are blocked or if JavaScript is disabled). Since the image size is not known, the script is inserted into the popup, which waits until the image has loaded before resizing to fit. Safari fires the body onload before the image has loaded, and Opera does not fire image onload if the image is in cache. Detecting onload for both allows the script to work in all major browsers:

//really not important (the first two should be small for Opera's sake)
PositionX = 10;
PositionY = 10;
defaultWidth  = 600;
defaultHeight = 400;

//kinda important
var AutoClose = true;

//don't touch
function popImage(imageURL,imageTitle){
  var imgWin = window.open('','_blank','scrollbars=no,resizable=1,width='+defaultWidth+',height='+defaultHeight+',left='+PositionX+',top='+PositionY);
  if( !imgWin ) { return true; } //popup blockers should not cause errors
  imgWin.document.write('<html><head><title>'+imageTitle+'<\/title><script type="text\/javascript">\n'+
    'function resizeWinTo() {\n'+
    'if( !document.images.length ) { document.images[0] = document.layers[0].images[0]; }'+
    'var oH = document.images[0].height, oW = document.images[0].width;\n'+
    'if( !oH || window.doneAlready ) { return; }\n'+ //in case images are disabled
    'window.doneAlready = true;\n'+ //for Safari and Opera
    'var x = window; x.resizeTo( oW + 200, oH + 200 );\n'+
    'var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;\n'+
    'if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }\n'+
    'else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }\n'+
    'else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }\n'+
    'if( window.opera && !document.childNodes ) { myW += 16; }\n'+
    'x.resizeTo( oW = oW + ( ( oW + 200 ) - myW ), oH = oH + ( (oH + 200 ) - myH ) );\n'+
    'var scW = screen.availWidth ? screen.availWidth : screen.width;\n'+
    'var scH = screen.availHeight ? screen.availHeight : screen.height;\n'+
    'if( !window.opera ) { x.moveTo(Math.round((scW-oW)/2),Math.round((scH-oH)/2)); }\n'+
    '}\n'+
    '<\/script>'+
    '<\/head><body onload="resizeWinTo();"'+(AutoClose?' onblur="self.close();"':'')+'>'+
    (document.layers?('<layer left="0" top="0">'):('<div style="position:absolute;left:0px;top:0px;display:table;">'))+
    '<img src="'+imageURL+'" alt="Loading image ..." title="" onload="resizeWinTo();">'+
    (document.layers?'<\/layer>':'<\/div>')+'<\/body><\/html>');
  imgWin.document.close();
  if( imgWin.focus ) { imgWin.focus(); }
  return false;
}

You would call the script like this:

<a href="me.jpg" onclick="return popImage(this.href,'Site author');">link</a>

Test it here: open an image in a perfect popup.

I also got some requests to make this contain a 'close' link. This is a little harder, since we need to measure the total size, not just the size of the image. So we need to include the referencing function as well and make a few other minor changes. This makes the script a bit bigger, so instead of putting it directly in here, I put it in a .js file so you can download it. This will also function nicely as the above script, you just leave out the extra HTML. This variant also includes an optional auto timeout before closing the popup.

Test it here: open an image with in a perfect popup.

A special note for Firefox: While resizing, the script needs some extra space around the edge of the window. For security reasons, some Firefox releases do not allow you to hide the address or tab bars. This takes up a fair amount of space. With large images (like the one I have used in this demonstration), this can actually run out of desktop space on 800 x 600 screen resolutions. This will not cause a problem, but it may leave a narrow gap underneath the pictures. Try not to use images as large as this (about 350-400 pixels high maximum) so that it does not leave this gap for Firefox users who use this screen resolution, or use the enhancement for automatic image resizing, as detailed below.

Other enhancements

The perfect popups serve many different purposes, and as a result, there are several modifications that you might want to make.

Centering the popup

I covered this in an earlier section. This was specifically written for the non-image popups, as the image popup code already contains this modification.

Making the popup use strict or XHTML doctype

While this is completely un-necessary, some people do feel the need to include these doctypes in the popup code. For the sake of simplicity, I do not recommend it, but if you do want to use one, you must also make another change to the generated content. The image should use display:block;

Note that due to a bug in Mozilla/Firefox/Gecko, if you write an XHTML doctype tag, and the XML prolog (declaration), but you use separate document.write commands to write them, Mozilla/Firefox/Gecko will use quirks mode rendering, not standards compliant mode.

Adding an animated 'Loading' graphic until the image has loaded

This is only relevant for the image popups. If you want to display an animated graphic that shows until the image has loaded, this is a simple case of attaching it to the background of the positioned element.

<div style="position:absolute;left:0px;top:0px;display:table;background: white url(loading.gif) no-repeat scroll top left;">

Remember that the image URL will be relative to the current page (the one that opens the popup).

It is also advisable to preload the image in the main page, to make sure it shows immediately.

var loadImg = new Image(); loadImg.src = 'loading.gif';

Preventing the popups from becoming larger than the screen

This one is a bit awkward, because if you prevent it becoming too big, you may need scrollbars. Unfortunately these can only be hidden or shown as the window is opened, and there is not much that we can do about it, so you will need to make sure the window is opened with scrollbars if this is going to happen. This involves changing the existing window.open command to set scrollbars=1. For non-image popups:

var x = window.open('','windowName','width=500,height=400,resizable=1,scrollbars=1');

For image popups:

var imgWin = window.open('','_blank','scrollbars=1,resizable=1,width='+defaultWidth+',height='+defaultHeight+',left='+PositionX+',top='+PositionY);

Now the code needs to be modified to make the adjustment. Before the line in the popup code containing this:

x.resizeTo( oW + 200, oH + 200 );

add this (you may need to play with the numbers in the first line). For image popups:

'var mH = screen.availHeight-50, mW = screen.availWidth-40;\n'+
'if( oH > mH ) { oH = mH; }\n'+
'if( oW > mW ) { oW = mW; }\n'+

For non-image popups:

var mH = screen.availHeight-50, mW = screen.availWidth-40;
if( oH > mH ) { oH = mH; }
if( oW > mW ) { oW = mW; }

Resizing the images to prevent them from becoming larger than the screen

This one is more easy, as we do not need to enable scrollbars. All that is needed is the code to check the screen size, and resize the image as required (this will not work in Netscape 4 - if you care).

If you are using the basic image popup; after this line:

'var oH = document.images[0].height, oW = document.images[0].width;\n'+

add this (you may need to play with the numbers in the first line):

'var mH = screen.availHeight-130, mW = screen.availWidth-40;\n'+
'if( oH > mH || oW > mW ) {\n'+
'mH = mH \/ oH; mW = mW \/ oW;\n'+
'var zoomFactor = ( mH < mW ) ? mH : mW;\n'+
'oH = Math.floor( oH * zoomFactor );\n'+
'oW = Math.floor( oW * zoomFactor );\n'+
'document.images[0].height = oH;\n'+
'document.images[0].width = oW;\n'+
'}\n'+

If you are using the image popup with the 'close' link, it is a bit more complicated. The image needs to be scaled, but the link text must not be. However, the calculation of maximum size relates to the total size and not just the image. If you want to work your head around this one, be my guest. If you just want something that (in theory) works, here it is. After this line:

'window.doneAlready = true;\n'+ //for Safari and Opera

add this (you may need to play with the numbers in the first line):

'var mH = screen.availHeight-130, mW = screen.availWidth-40;\n'+
'if( oH > mH || oW > mW ) {\n'+
'var hDif = oH - document.images[0].height;\n'+
'var wDif = oW - document.images[0].width;\n'+
'mH = mH - hDif; mW = mW - wDif;\n'+
'mH = mH \/ document.images[0].height;\n'+
'mW = mW \/ document.images[0].width;\n'+
'var zoomFactor = ( mH < mW ) ? mH : mW;\n'+
'oH = Math.floor( document.images[0].height * zoomFactor );\n'+
'oW = Math.floor( document.images[0].width * zoomFactor );\n'+
'document.images[0].height = oH;\n'+
'document.images[0].width = oW;\n'+
'oH += hDif; oW += wDif;\n'+
'}\n'+

Toggling between resized and full size is also possible. This modification assumes you have already added the appropriate resizing code as shown above, and you have set scrollbars to '1' when opening the window. This is a farily large modification, because it is required to resize the window to fit, reposition it, and resize the image, meaning that this addition alone contains nearly as much code as the original script (some properties are saved for future use). After this line:

'if( oH > mH || oW > mW ) {\n'+

add this:

'document.images[0].fullH = oH;\n'+
'document.images[0].fullW = oW;\n'+

and before this line:

'document.images[0].height = oH;\n'+

add this:

'document.images[0].style.cursor = \'crosshair\';\n'+
'document.images[0].title = \'Click to resize image\';\n'+
'document.images[0].oldHeight = document.images[0].height;\n'+
'document.images[0].oldWidth = document.images[0].width;\n'+
'document.images[0].newHeight = oH;\n'+
'document.images[0].newWidth = oW;\n'+
'document.images[0].onclick = function () { '+
'if( this.oldHeight == this.height ) { '+
'this.height = this.newHeight; this.width = this.newWidth; '+
//the timeout is to avoid a Mozilla/Firefox scrollbar bug
'setTimeout(\'window.resizeTo(\'+this.oW+\',\'+this.oH+\'); '+
'if( !window.opera ) { '+
'window.moveTo(\'+Math.round((this.scW-this.oW)/2)+\','+
'\'+Math.round((this.scH-this.oH)/2)+\'); }\',1);\n'+
'} else { this.height = this.oldHeight; this.width = this.oldWidth; '+
'window.moveTo(0,0); window.resizeTo(this.scW,this.scH); '+
//after maximizing, remove any extra space around the image
'var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;\n'+
'if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }\n'+
'else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }\n'+
'else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }\n'+
'if( window.opera && !document.childNodes ) { myW += 16; }\n'+
'if( this.fullH < myH ) { var rs = this.fullH - myH; window.resizeBy(0,rs); '+
'if( !window.opera ) { window.moveBy(0,Math.round(rs/-2)); } }'+
'if( this.fullW < myW ) { var rs = this.fullW - myW; window.resizeBy(rs,0); '+
'if( !window.opera ) { window.moveBy(Math.round(rs/-2),0); } }'+
'} };\n'+

and after this line:

'var scH = screen.availHeight ? screen.availHeight : screen.height;\n'+

add this:

'document.images[0].oH = oH;\n'+
'document.images[0].oW = oW;\n'+
'document.images[0].scH = scH;\n'+
'document.images[0].scW = scW;\n'+

You can download the image size toggle version of the script, or test it here: open an image popup with togglable size.

Note, if you want the windows to start in maximised mode, after this line:

(oTimeClose?('window.setTimeout(\'window.close()\','+oTimeClose+');\n'):'')+

add this:

'document.images[0].onclick();\n'+

Resizing the popups to fit non-image content of unknown width

This is only for the non-image popups. This is probably the hardest of all the modifications, as the resizing code assumes that the width is known or fixed. The content should be a table (because other block level elements will always try to use the maximum width available, where a table will shrink to fit the required width). Because you are not forcing a certain width, the table will reformat very strangely, which will really confuse the script. As it resizes the first time, the table changes size as well. The script does not know this, so you need to resample the height and width of the div before you resize again.

Also, as the page may start with scrollbars - especially if the window starts small, and the table readjustments may change that (unlike when the width is fixed), I feel it is a good idea to resize the window to its maximum available space first, even if it is not always necessary.

Opera 7 needs you to set the padding on the body tag to 0, and IE needs you to set the margin to 0. Firefox needs a border on the table or it gets some of the calculations wrong and shows scrollbars.

<body ... style="padding:0;margin:0;">

To make the modification, a substantial amount of the code must be changed. Firstly, remove the parts that specify the width. Then everything after the first occurence of this:

if( !oH ) { return false; }

must be replaced with:

x.resizeTo( screen.availWidth, screen.availWidth );
var oW = oH.clip ? oH.clip.width : oH.offsetWidth;
var oH = oH.clip ? oH.clip.height : oH.offsetHeight; if( !oH ) { return false; }
x.resizeTo( oW + 200, oH + 200 );
var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;
if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }
else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }
else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }
if( window.opera && !document.childNodes ) { myW += 16; }
//second sample, as the table may have resized
var oH2 = getRefToDivMod( idOfDiv );
var oW2 = oH2.clip ? oH2.clip.width : oH2.offsetWidth;
var oH2 = oH2.clip ? oH2.clip.height : oH2.offsetHeight;
x.resizeTo( oW2 + ( ( oW + 200 ) - myW ), oH2 + ( (oH + 200 ) - myH ) );
}

Test it here: open a popup that resizes to fit unknown content.

This site was created by Mark "Tarquin" Wilton-Jones.
Don't click this link unless you want to be banned from our site.