Set IFRAME height based on size of remotely loaded content

Update 13-Mar-2010: After writing this I discovered a bug which occasionally caused the iframe to be sized incorrectly -- subsequently a number of others had the same problem. I was unable to determine the cause, but implemented a work-around solution. Redux: Redux: Set IFRAME height based on size of remotely loaded content.

When you load a remote page (a page which is not on your domain) into an IFRAME how do you ensure that the IFRAME expands its height to completely wrap its content, and has no vertical scroll bars (avoids having both the browser scroll bar and an iframe scroll bar), regardless of the content loaded? Well unless you have the willing consent of both domain on which the remote content is stored, you don't. But, if you can all agree to get along, then it is possible to seamlessly load remote content into your page, with no scrollbars, borders, or other visual queues of content being stored elsewhere.

The technique for cross-domain communication is used by Facebook, iGoogle, and Google Maplets, but there doesn't seem to be wide recognition of when and how it can be used.

Works across browsers, including Chrome, Firefox, Opera, Safari, and even Internet Explorer 6/7/8.


The primary problem is that there is no way for the parent page read or set properties from iframed content. There is also no way for iframed content on another domain to set properties on a parent. This apparent inability to communicate between content is the source of the problem. How do we get the height of the iframed content from the iframe back to the parent?

The Rules

Browser adhere to security policies which dictate the rules for communication between framed content. In these rules a window refers either to an iframe or the top-level window (i.e. the “main” page).

  1. A window in the hierarchy can reference any other window in the hierarchy.
  2. A window can only access another windows internal state if they belong to the same domain.
  3. A window can set (but not read) any other window’s location/URL. (Yes, this means a child frame can set the parent windows URL -- useful for busting a site out of an iframe.)

The Example

In the model above we have the parent window containing http://local.com/local.html, which contains a Local Iframe with a page loaded from a remote domain, remote.com/remote.html. Remote.html itself contains a Hidden Iframe, which loads content from local.com/helper.html:

local.com/local.html, which iframes
|---> remote.com/remote.html, which iframes
      |---> local.com/helper.html
  • local.com/local.html can communicate with remote.com/remote.html, since it's iframed
  • remote.com/remote.html can communicate with local.com/helper.html, since it's iframed
  • remote.com/remote.html cannot communicate with local.com/local.html, because they are not on the same doamin
  • local.com/helper.html can communicate with local.com/local.html, because they are on the same domain
  • (Not relevant for this example, but local.com/local.html can communicate with local.com/helper.html, since they are on the same domain)

So local.com/helper.html can recieve messages from remote.com/remote.html, and can also communicate with local.com/local.html.

In Practice

How do we put this to use? Specificaly how do we communicate the height of the content in remote.com/remote.html back up to local.com/local.html so we can set the height of Local Iframe? From the steps in the diagram above:

  1. User loads local.com/local.html
  2. local.com/local.html contains a Local Iframe, which loads remote.com/remote.html. remote.com/remote.html has a Hidden Iframe, with no content loaded into it yet.
  3. When remote.com/remote.html has finished loading, its onload events fires. Now we calculate the height of remote.com/remote.html, and set Hidden Iframes src attribute to local.com/helper.html?height=XXX.
  4. Once the content of local.com/helper.html?height=XXX has finished loading, its onload events fires. The value of height is parsed from the URL, and the height of Local Iframe in remote.com/remote.html is set.

The Code

This code structure assumes that you have access to the local domain, and are able to provide files and request changes to the remote domain. If you don't have the consent of the remote domain this isn't going to work for you, so abandon all hope.

http://local.com/local.html

If the situation is reversed and you own the remote domain, then you'll want to split the embedded javascript out, and store it in a file on the remote domain. That way you can change it in the future if needed, without having to get the local domain to make any changes.

The height and width of Local Iframe are set in order to keep it hidden until the http://remote.com/remote.html is loaded and the height is set. IE7 has problems setting the height of an iframe if it's initial height is 0, that's why the iframe is sized at 1px.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript">
function resizeIframe(height){
	// "+60" is a general rule of thumb to allow for differences in
	// IE & and FF height reporting, can be adjusted as required
	document.getElementById('local-iframe').height = parseInt(height)+60;
	document.getElementById('local-iframe').width = '100%';
}
</script>
</head>
<body>

<div>base site content</div>

<iframe id='local-iframe' width='1' height='1' frameborder='0' src='http://remote.com/remote.html'></iframe>

</body>
</html>

http://remote.com/remote.html

The remote domain needs to add a hidden iframe to the page, and needs to call iframeResizePipe() onload.

An additional 'random value' parameter is added to the http://local.com/helper.htm in order to prevent a cahched URL from being returned. This is not 100% fool-proof, but it's probably good enough.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript">
function iframeResizePipe(){
	var height = document.body.parentNode.scrollHeight;

	// Going to 'pipe' the data to the parent through the helpframe.
	var pipe = document.getElementById('helpframe');

	// Cachebuster a precaution here to stop browser caching interfering
	pipe.src = 'http://local.com/helper.html?height='+height+'&cacheb='+Math.random();
}
</script>
</head>
<body onload="iframeResizePipe()">
	<iframe id="helpframe" src='' height='0' width='0' frameborder='0'></iframe>
</body>
</html>

http://local.com/helper.html

This page is on the same domain as the parent, so it can access page attributes from the parent, resizing the iframe window to fit the content.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript">
	// Tell the parent iframe what height the iframe needs to be
	function parentIframeResize(){
		var height = getParam('height');
		// This works as our parent's parent is on our domain
		parent.parent.resizeIframe(height);
	}

	// Helper function, parse param from request string
	function getParam( name ){
		name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
		var regexS = "[\\?&]"+name+"=([^&#]*)";
		var regex = new RegExp( regexS );
		var results = regex.exec( window.location.href );
		if( results == null ) return "";
		else return results[1];
	}
</script>
<body onload="parentIframeResize()">
</body>
</html>

Based on information from MSDN Architecture Center, Stack Overflow, and Softwareas.

Comments

  1. By Peter, on July 28, 2009, at 08:04 PM
    Great article! Just what I was looking for. Thanks for sharing. I noticed Google Custom Search result page iframed in another domain does something similar as well, but not sure how they accomplished that.
  2. By searcher, on August 13, 2009, at 03:33 AM
    This is a very nice article it saves our day try to solve iframe autresize issues!Thanks a lot and keep helping others..
  3. By Andrew, on August 13, 2009, at 09:53 AM
    I tried this and it did something completely different in FF to IE (firefox made the frame enormous, IE set it to the 60px defined in the local.html JS function).

    Also, is the missing </head> tag deliberate?
  4. By David Gilbert, on August 13, 2009, at 03:34 PM
    @searcher: Glad it helped!

    @Andrew: Missing head tag is a typo; thanks for noting it. I haven't seen the specific issue you describe where IE height is set to 60px & FF very large, but we did see another issue where multiple page refreshes caused the iframe to grow, the iframe being too small, and also with scrollbars. I'll update the article with info on how we resolved those.

    Let us know how/if you resolve your problem!
  5. By Ryan, on August 20, 2009, at 10:06 PM
    Very helpful article, thanks.

    I experienced almost the same issue as Andrew. IE(7) is making the frame only 60px high, though FF(3.5) is essentially rendering correctly. My "local" is an intranet server and my "remote" is a SharePoint server (still within the corporate network). Had to use a content editor web part and a special SharePoint function (http://blogs.msdn.com/saurabhkv/archive/2009/06/22/javascript-pageload-add-function.aspx) to do the onLoad, but it works in FF!
  6. By Jon Virgi, on August 23, 2009, at 01:56 PM
    Thanks for the help! This worked very well but I've noticed a few things.

    -In Chrome and FF, the height grows as i click through the pages within the iframe.
    -In IE if the frame is too small; i changed the +60px to +0 and the iframe is 1px in height......which means to me that IE is not working with this script?

    Please let me know if you have some solutions.
  7. By David Gilbert, on August 27, 2009, at 04:41 AM
    @Jon Virgi: I also had the height growing issue. Removing the +60px padding resolved the issue and didn't appear to have any adverse effects.

    Not sure what you mean in your second bullet. You mean that in IE the height never changes -- the same issue as Ryan? For what it's worth, I have things working fine in IE, so it is possible.

    @Ryan: If the iframe is only 60px high, that suggests you have an issue with the communication of the height from the 'local' page to the 'remote'. Best I can suggest is plenty of alert statements : )
  8. By Jon Virgi, on August 27, 2009, at 02:40 PM
    Yes, the 2nd bullet was same issue as Ryan and Andrew.... I guess IE is not doing the onload resize like the other browsers are. I cant think of any server differences like Ryans case though.... Local is http://www.cincinnatisquash.com/cmsms/index.php?page=squashwars-test and remote is www.racquetwars.com/us/cinci

    Also, looks like when i view in Chrome, the page will autosize to get larger, but not smaller meaning, if you view a 800px long page, then go to a 500px long one, the iframe remains at 800px.

    Thanks!
  9. By Gareth, on January 06, 2010, at 11:56 AM
    Brilliant!

    Thank you for posting this article online. It works great on my website!
  10. By Amit, on January 22, 2010, at 08:59 AM
    Great article, David! But as Jon Virgi noted in his comment, the iframe never resizes to a lower size in chrome. As such, there are no iframe scrollbars, but looks wierd when the page has a lot of unused space for shorter iframe contents. As far as the code is concerned, the problem lies with 'document.body.parentNode.scrollHeight' that doesn't give correct height if the new content height is less than previous content height. Do you have a solution for this?
  11. By David Gilbert, on January 26, 2010, at 01:43 PM
    @Amit/Virgi: I missed Virgi's original comment, sorry. I also experienced the problem of detecting an incorrect height. I was unable to work out the cause. It appeared to be something to do with the content still loading during code execution. On that assumption I added a polling mechanism that kicked off every 0.x seconds and checked to see if the height was static for some period of time. Not a clean solution, but it did resolve the problem. I'll update the article with the fix.
  12. By Nick Wilcox, on March 13, 2010, at 06:38 AM
    Hello

    -looks like exactly what I need thank you for this tutorial! Have you looked at the issue mentioned by Amit and Virgi?

    amazing job kudos!
  13. By David Gilbert, on March 14, 2010, at 03:36 AM
    @Nick Wilcox: I'll do a quick write up of the work around in a few days.
  14. By Nick Wilcox, on March 14, 2010, at 06:01 AM
    Thanks David. Just wanted to reply that your approach/setup works fantastic and was easy to integrate. Got it working on the first attempt!
  15. By Sam Sehnert, on June 24, 2010, at 01:01 AM
    One trick that I'm using, is to load in the local/helper.html once, and just change the hash - ie, instead of using the ?width= parameter, Im doing this:

    <script type="text/javascript">
    	// Tell the parent iframe what height the iframe needs to be
    
    	var lastHash = 0;
    	var lastHashThatWasntZero = 200;
    
    	// This works as our parent's parent is on our domain
    	setInterval( function(){
    
    		var newHash = Number( window.location.hash.replace('#','') );
    
    		if( newHash !== lastHash ){
    			parent.parent.resizeIframe( newHash == 0 ? lastHashThatWasntZero : newHash );
    			lastHash = newHash;
    			if( newHash !== 0 ) lastHashThatWasntZero = newHash;
    		}
    	}, 10 );
    
    </script>
    


    This way, whenever my remote page changes height (I have a similar script in my remote page which keeps checking the height) I just need to change the hash of the iframe in the remote page - so instead of reloading the entire script each time, it just picks up the hash change, and tells the parent frame to resize.
  16. By Erik Malson, on July 31, 2010, at 07:30 PM
    I actually just sent you a friend request on FB. Could you send me an email at the above email address?

    I want to know if you could adapt your script for a project I am working on and how much you'd charge me...

    I'm a novice programmer but from what I understand, and if I THINK I'm reading this right, it probably wouldnt take you more than 15 minutes or so to modify your script to work with my site.

    I thank you for the consideration in advance...

    -- Erik
  17. By Kerri, on September 03, 2010, at 05:18 PM
    Thank your for this awesome solution David!!

    I'm having an issue with this script in the remote helper file:

    pipe.src = 'http://local.com/helper.html?height=height&cacheb='+Math.random();

    It seems to be interfering with the multi-select option on our pages. Any suggestions?
  18. By daveg, on September 03, 2010, at 06:09 PM
    What do you mean 'interferes with multi-select list'? What do you see, or what happens? And what makes you think it's that line in particular that is causing the problem?
  19. By Kerri, on September 03, 2010, at 06:30 PM
    Hi David,
    Please disregard my previous post. We discovered it was some scripts on the remote side that were interfering. Your scripts work great!
  20. By Ignacio, on February 23, 2011, at 08:13 AM
    Very clever! Smart. I like this.
  21. By Micah, on April 08, 2011, at 05:40 PM
    For those using jQuery, here is a simplified way of doing this

    function fncResizeIframe() {
    var theFrame = $('#frame', parent.document.body);
    theFrame.height($(document.body).height() + 30)
    }

    where "frame" is the ID of your iframe on your parent page. This function is called from your page loaded up in the iframe.
  22. By Ricardo Cobra, on June 19, 2011, at 03:03 AM
    My solution with nested iframes:

    function fncResizeNIframes() {
     document.getElementById('iframe1').height=document.getElementById('iframe1').contentWindow.document.body.scrollHeight; 
     parent.document.getElementById('iframe0').height= this.document.height+30;
    }
    

  23. By Balajiprasad, on August 03, 2011, at 11:40 AM
    Hi,

    I used this concept and implemented, for my case iam using tabs.. once the page loads ie)remote.html it contains tab & difference height of content. How to handle this case. because we dont have any server hits once the remote.html loaded.
  24. By Kerri, on September 19, 2011, at 10:31 PM
    Hi David,
    When we click through the pages inside the iFrame on our pages, it loads the new page at the top of the iFrame which is around 200px below the top of the page that houses the iFrame. Do you know how to correct this?
  25. By Adam, on January 23, 2012, at 03:48 PM
    Great! I've tried so many different ways of doing this over the past few days and this is the only one that's worked!! THANK YOU!!!
  26. By Raiden, on February 01, 2012, at 07:33 AM
    Just wanted everyone to know that this information did not work for me and the iframe height was always set to height="NaN".

    HOWEVER, I did find some code that DID work extremely well and can be found here: http://www.ittreats.com/os/javascript/auto-resizing-iframe-height-in-the-page.html

    Cheers
  27. By paul, on May 11, 2012, at 12:22 PM
    This works PERFECT! thanks a lot.