22

I have an iframe as you can see on the following link;-

http://one2onecars.com

The iframe is the online booking in the centre of the screen. The problem I have is that although the height of the iframe is okay as the page loads, I need it to somehow auto adjust the height as the page content adjusts. For example, if I do a postcode search in the online booking it creates a dropdown menu and then makes the 'Next Step' button not viewable.

What I need to happen is that when the content of the online booking changes, the iframe auto adjusts to the new height of the iframe (dynamically) as it is not loading any other pages.

I have tried several different scripts using jquery to try resolving this issue, but they all only seem to auto adjust the height of the iframe when the page first loads and not as the contents of the iframe changes.

Is this even possible to do?

The code I have at the moment is with a set height at the moment:-

        <div id="main-online-booking">

            <iframe id="main-online-frame" class="booking-dimensions" src="http://www.marandy.com/one2oneob/login-guest.php" scrolling="no" frameborder="0"></iframe>

        </div>

#main-online-booking {
    height: 488px;
    border-bottom: 6px #939393 solid;
    border-left: 6px #939393 solid;
    border-right: 6px #939393 solid;
    z-index: 4;
    background-color: #fff;
}


.booking-dimensions {
    width: 620px;
    height: 488px;
}

If anybody can help me with this I would be much appreciated!

David Bradshaw
  • 10,116
  • 3
  • 33
  • 61
nsilva
  • 4,016
  • 13
  • 50
  • 95
  • see this solution http://stackoverflow.com/questions/11266574/expand-an-iframe-during-jquery-animation-when-contents-expand/14246632#14246632 – algorhythm Jan 15 '13 at 09:07
  • why use Iframe in first place? ONly take a little bit of code to convert Iframe module to AJAX – charlietfl Jan 15 '13 at 09:10
  • Because the online booking is hosted on our servers and not the customers – nsilva Jan 15 '13 at 09:11
  • 1
    if Iframe going on another domain can't use script between frame and main document due to security restrictions – charlietfl Jan 15 '13 at 09:12
  • 1
    different domain makes AJAX even more appealing..use `jsonp` to tarnsfer data – charlietfl Jan 15 '13 at 09:15
  • This is what I came up with: var height = 0; $('iframe').contents().filter(function() { if($(this).height() > height) height = $(this).height(); }); $('iframe').css('height',height +'px'); – Jiskiras Jan 15 '13 at 09:19

4 Answers4

30

setInterval

The only (corrected due to advances in browser tech, see David Bradshaw's answer) backwards compatible way to achieve this with an iframe is to use setInterval and keep an eye on the iframe's content yourself. When it changes its height, you update the size of the iframe. There is no such event you can listen out for that will make it easy unfortunately.

A basic example, this will only work if the iframe content that has changed in size is part of the main page flow. If the elements are floated or positioned then you will have to target them specifically to look for height changes.

jQuery(function($){
  var lastHeight = 0, curHeight = 0, $frame = $('iframe:eq(0)');
  setInterval(function(){
    curHeight = $frame.contents().find('body').height();
    if ( curHeight != lastHeight ) {
      $frame.css('height', (lastHeight = curHeight) + 'px' );
    }
  },500);
});

Obviously depending on what you want you can modify the perspective of this code so that it works from the iframe, on itself, rather than expecting to be part of the main page.

cross-domain issue

The problem you will find is that due to browser security it wont let you access the content of the iframe if it is on a different host to the main page, so there isn't actually anything you can do unless you have a way of adding any script to the html that appears in the iframe.

ajax

Some others are suggesting trying to use the third-party service via AJAX, unless the service supports this method it will be very unlikely you'll be able to get it to work -- especially if it is a booking service that will most likely need to operate over https/ssl.

As it appears you have full control over the iframe content, you have full options open to you, AJAX with JSONP would be an option. However, one word of warning. If your booking system is multistepped you need to make sure you have a well designed UI -- and possibly some history/fragment management code -- if you are to go down the AJAX route. All because you can never tell when a user will decide to navigate forward or back in their browser (which an iframe would automatically handle, within reason). A well designed UI can detract users from doing this.

cross-domain communication

If you have control of both sides (which it sounds like you do) you also have the cross domain communication option using window.postMessage - see here for more information https://developer.mozilla.org/en-US/docs/DOM/window.postMessage

Community
  • 1
  • 1
Pebbl
  • 31,117
  • 6
  • 57
  • 63
  • last statement about AJAX is absurd. OP is providing the service and there is absolutely no reason they can't provide a `jsonp` API to accomplish same thing – charlietfl Jan 15 '13 at 09:25
  • any sensitive data can simply be managed on a different page... main page doesn't need to perform everything. But for basic search UI on main page would be ideal – charlietfl Jan 15 '13 at 09:34
  • @charlietfl hardly absurd, where in the question does it mention the OP is providing the iframe content? I admit I missed the later comment, but the question doesn't mention it, and if dealing with a third-party what I state is perfectly true (especially as I constantly mention third-party). However, good point, I shall modify my answer accordingly. I have no idea what you are on about with your second comment though... – Pebbl Jan 15 '13 at 09:36
  • suggestion about ajax has nothing to do with third party. That was an assumption on your part. For OP situation removing Iframe makes more sense than keeping it IMO. Comment about sensitive data was in regard to your `https` comments...more than one way to skin a cat. At point where any sensitive data would need to be input, open another page that doesn't have UI interference issues when using iframe – charlietfl Jan 15 '13 at 09:39
  • @DavidBradshaw ~ Others have rejected your edit because you should post such involved additional detail as a new answer, especially if it has quite different content. If you do, I'd quite happily up vote your additions myself as they are clear and up-to-date (at least with modern browsers). – Pebbl Mar 21 '14 at 18:46
  • @pebbl, ok have written a new longer answer. – David Bradshaw Apr 10 '14 at 15:35
  • @DavidBradshaw ~ nicely put together +1 from me. – Pebbl Apr 10 '14 at 15:58
25

Modern browser and in part IE8 have some new features that make this task easier than it use to be.

PostMessage

The postMessage API provides a simple method for comunicating between an iFrame and it's parent.

To send a message to the parent page you call it as follows.

parent.postMessage('Hello parent','http://origin-domain.com');

In the other direction we can send the message to the iFrame with the following code.

var iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage('Hello my child', 'http://remote-domain.com:8080');

To recevie a message create an event listerner for the message event.

function receiveMessage(event)
{
  if (event.origin !== "http://remote-domain.com:8080")
    return;

  console.log(event.data);
}

if ('addEventListener' in window){
    window.addEventListener('message', receiveMessage, false);
} else if ('attachEvent' in window){ //IE
    window.attachEvent('onmessage', receiveMessage);

These examples uses the origin property to limit where the message is sent to and to check where it came from. It is possible to specify * to allow sending to any domain and you may in some cases you may want to accept messages from any domain. However, if you do this you need to consider the security implications and implement your own checks on the incoming message to ensure it contains what your expecting. In this case the iframe can post it's height to '*', as we might have more than one parent domain. However, it's a good idea to check incoming messages are from the iFrame.

function isMessageFromIFrame(event,iframe){
    var
        origin  = event.origin,
        src     = iframe.src;

    if ((''+origin !== 'null') && (origin !== src.substr(0,origin.length))) {
        throw new Error(
            'Unexpect message received from: ' + origin +
            ' for ' + iframe.id + '. Message was: ' + event.data  
        );
    }

    return true;
}

MutationObserver

The other advance in more modern broswers is MutationObserver which allows you to watch for changes in the DOM; so it is now possible to detect changes that could effect the size of the iFrame without having to constantly poll with setInterval.

function createMutationObserver(){
    var
        target = document.querySelector('body'),

        config = {
            attributes            : true,
            attributeOldValue     : false,
            characterData         : true,
            characterDataOldValue : false,
            childList             : true,
            subtree               : true
        },

        observer = new MutationObserver(function(mutations) {
            parent.postMessage('[iframeResize]'+document.body.offsetHeight,'*');
        });

    log('Setup MutationObserver');
    observer.observe(target, config);
}

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

if (MutationObserver){
    createMutationObserver();
}

Working out an accurate height

Getting an accurate height for the iFrame is not as simple as it should be, as you have a choice of six different properties that you can check and none of them give a constantly right answer. The best solution I've come up with is this function that works so long as you don't use CSS to overflow the body tag.

function getIFrameHeight(){
    function getComputedBodyStyle(prop) {
        return parseInt(
            document.defaultView.getComputedStyle(document.body, null),
            10
        );
    }

    return document.body.offsetHeight +
        getComputedBodyStyle('marginTop') +
        getComputedBodyStyle('marginBottom');
}

This is the IE9 version, for the much long IE8 version see this answer.

If you do overflow the body and you can't fix your code to stop this, then using either the offsetHeight or scrollHeight properties of document.documentElement are your best options. Both have pros and cons and it best just to test both and see which works for you.

Other issues

Other things to consider include, having more than one iFrame on the page, CSS :Checkbox and :Hover events causing page resize, avoiding the use of height auto in the iFrames' body and html tags and lastly the window being resized.

IFrame Resizer Library

I've wrapped all this up in a simple dependancy free library, that also provides some extra functions not discussed here.

https://github.com/davidjbradshaw/iframe-resizer

This works with IE8+.

Community
  • 1
  • 1
David Bradshaw
  • 10,116
  • 3
  • 33
  • 61
  • 1
    In the function `getComputedBodyStyle` you define a parameter `prop`, however in the function body it is not used. Typo somewhere? – Geert Nov 25 '14 at 09:27
  • Old comment, but fwiw there's a missing call to `getPropertyValue(prop)`. So, the way to get the computed style property is: `document.defaultView.getComputedStyle(document.body, null).getPropertyValue(prop)`. – Ryan Wilson Aug 25 '16 at 23:39
  • 2
    Holy crap, thank you for the resizer library. That was completely simple to set up and works perfectly. – Jimmy Sep 30 '16 at 16:34
  • 1
    This might be a better way to get height, `document.body.getBoundingClientRect().height` – Eddie Oct 26 '18 at 01:44
  • @Eddie Interesting idea – David Bradshaw Dec 13 '18 at 18:58
7

I wrote this script and it's working perfectly for me. Feel free to use it!

function ResizeIframeFromParent(id) {
    if (jQuery('#'+id).length > 0) {
        var window = document.getElementById(id).contentWindow;
        var prevheight = jQuery('#'+id).attr('height');
        var newheight = Math.max( window.document.body.scrollHeight, window.document.body.offsetHeight, window.document.documentElement.clientHeight, window.document.documentElement.scrollHeight, window.document.documentElement.offsetHeight );
        if (newheight != prevheight && newheight > 0) {
            jQuery('#'+id).attr('height', newheight);
            console.log("Adjusting iframe height for "+id+": " +prevheight+"px => "+newheight+"px");
        }
    }
}

You can call the function inside a loop:

<script>
jQuery(document).ready(function() {
    // Try to change the iframe size every 2 seconds
    setInterval(function() {
        ResizeIframeFromParent('iframeid');
    }, 2000);
});
</script>
Bram Verstraten
  • 1,269
  • 10
  • 20
0

use this script:

$(document).ready(function () {
  // Set specific variable to represent all iframe tags.
  var iFrames = document.getElementsByTagName('iframe');

  // Resize heights.
  function iResize() {
    // Iterate through all iframes in the page.
    for (var i = 0, j = iFrames.length; i < j; i++) {
    // Set inline style to equal the body height of the iframed content.
      iFrames[i].style.height = iFrames[i].contentWindow.document.body.offsetHeight + 'px';
    }
  }

 // Check if browser is Safari or Opera.
 if ($.browser.safari || $.browser.opera) {
    // Start timer when loaded.
    $('iframe').load(function () {
    setTimeout(iResize, 0);
    });

   // Safari and Opera need a kick-start.
   for (var i = 0, j = iFrames.length; i < j; i++) {
     var iSource = iFrames[i].src;
     iFrames[i].src = '';
     iFrames[i].src = iSource;
   }
 } else {
    // For other good browsers.
    $('iframe').load(function () {
    // Set inline style to equal the body height of the iframed content.
    this.style.height = this.contentWindow.document.body.offsetHeight + 'px';
    });
  }
});

Note : use it on webserver.

Jai
  • 71,335
  • 12
  • 70
  • 93
  • note that will only work on same domain. Appears OP wants Iframe on other domains – charlietfl Jan 15 '13 at 09:13
  • so this will work on an iframe on the same domain? if thats the case I'll have to get the client to transfer the domain to us – nsilva Jan 15 '13 at 09:19
  • 1
    @nsilva This code will only work onload, as it isn't using any polling method, as far as I can see. – Pebbl Jan 15 '13 at 09:20