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.

Set IFRAME Height Based On Size Of Remotely Loaded Content 1


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