cross-domain iframe resize

92,398

Solution 1

The thing is - there is no other way than using Cross-Domain Messaging for this since you need to get the computed height from a document in one domain, to a document in a different domain.

So, either you do this using postMessage (works in all moder browsers), or you spend 5 minutes adapting the resize iframe example from easyXDM.

The other party really just needs to copy a few files onto their domain, and add a single line of code to their document..

Solution 2

Similar to what Sean has mentioned, you can use postMessage. I've spent so much time trying different ways to resize iframe with cross-domain but no luck until I stumbled on this great blog post by David Walsh: http://davidwalsh.name/window-iframe

This is a combination of my code and David's solution. My solution is geared specifically toward resizing iframes.

In the child page, find the height of the page and pass it to the parent page (which contains the iframe). Replace element_id with your element id (html, body, whatever).

<script>
function adjust_iframe_height(){
    var actual_height = document.getElementById('element_id).scrollHeight;
    parent.postMessage(actual_height,"*"); 
    //* allows this to post to any parent iframe regardless of domain
}
</script>

<body onload="adjust_iframe_height();"> 
//call the function above after the content of the child loads

On the parent window, add this code. Replace iframe_id with your iframe ID:

<script>
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

// Listen to message from child window
eventer(messageEvent,function(e) {
  console.log('parent received message!:  ',e.data);
  document.getElementById('iframe_id').height = e.data + 'px';
},false);
</script>

If you open the console, you will see the height being printed in the console log. This will help you in debugging which is why I left it there.

Best, Baker

Solution 3

Having looked a lots of different solution to this I ended up writing a simple small library to take a account of a number of different use cases. As I needed a solution that supported multiple user generated iFrames on a portal page, supported browser resizes and could cope with the host page JavaScript loading after the iFrame. I also add support for sizing to width and a callback function and allow the override of the body.margin, as you will likely want to have this set to zero.

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

The iframe code is just a little self-contained JavaScript, so that it's a good guest on other people pages.

The script is then initialised on the host page with the following available options.

iFrameResize({
    log                    : true,  // For development
    autoResize             : true,  // Trigering resize on events in iFrame
    contentWindowBodyMargin: 8,     // Set the default browser body margin style (in px)
    doHeight               : true,  // Calculates dynamic height
    doWidth                : false, // Calculates dynamic width
    enablePublicMethods    : true,  // Enable methods within iframe hosted page 
    interval               : 32,    // interval in ms to recalculate body height, 0 to disable refreshing
    scrolling              : false, // Enable the scrollbars in the iFrame
    callback               : function(messageData){ // Callback fn when message is received
        $('p#callback').html(
            '<b>Frame ID:</b> '    + messageData.iframe.id +
            ' <b>Height:</b> '     + messageData.height +
            ' <b>Width:</b> '      + messageData.width + 
            ' <b>Event type:</b> ' + messageData.type
        );
    }
});
Share:
92,398
Piotr Kula
Author by

Piotr Kula

Back in the day... First computer - IBM XT 4mhz with 8mhz turbo running on MS-DOS 2.0 First application - Blackjack, with SVGA graphics written in TurboPascal Presently... Published a book about the Raspberry Pi and about Raspberry Pi 2 Building electrical gadgets in spare time. Blog CEO of Kula Solutions LTD Microsoft Certified Solutions Developer, creating world class solutions for global companies. Some inspiration... Joel Spolsky Elon Musk

Updated on July 08, 2022

Comments

  • Piotr Kula
    Piotr Kula almost 2 years

    How do I resize an iframe from another domain

    -Edit

    Scroll down for some solutions.. or read on how NOT to do this :D

    After many hours of code hacking- the conclusion is that anything inside the iframe is not accessible, even the scrollbars that render on my domain. I have tried many techniques to no avail.

    To save you time don't even go down this route just use sendMessages for cross domain communications. There are plug ins for HTML < 5 that I use- Go to the bottom for a nice example :)


    Past few days I have been trying to integrate an iframe into a site. This is a short term solution while the other side develops and API(could take months...) And because this is as short term solution we done want to use easyXDM- I have access to the other domain but its difficult enough asking them to add p3p header as it is.....

    3 iframes

    The closest solution I found was the 3 iframes- but it goes mental of chrome and safari so I cannot use that.

    open in chrome

    http://css-tricks.com/examples/iFrameResize/crossdomain.php#frameId=frame-one&height=1179

    Measure the scrollbars

    I found a another post on how to use the scrollheight to try and resize the form.. in theory it works well but I could not apply it properly using the iframes scroll height..

    document.body.scrollHeight
    

    That obvoisly uses the bodies height (cannot access these properties 100% is based on the clients display canvaz and not the x-domains document height)

    I tried using jquery to get the iframes height

    $('#frameId').Height()
    
    $('#frameId').clientHeight
    
    $('#frameId').scrollHeight
    

    return values different in chrome and ie - or just don't make sense at all. The problem is that everything inside the frame is denied- even the scrollbar...

    Computed Styles

    But if I inspect and element in chrome of the iframe it bladdy shows me the documents dimensions inside the iframe (using jquery x-domain to get iframe.heigh - access denied) There is nothing in the computed CSS enter image description here

    Now how does chrome calculate that? (edit- browser re-renders the page using its build in rendering engine to calcualte all these settings - but are not attached anywhere to prevent cross-domain fraud.. so..)

    HTML4

    I read specification of HTML4.x and it says there that there should be read-only values exposed via document.element but it's access denied via jquery

    Proxy Frame

    I went down the route of proxying the site back and calculating which is OK.. until a user logs in through the iframe and the proxy gets a login page instead of the actual content. Also to some calling the page twice is not acceptable

    http://www.codeproject.com/KB/aspnet/asproxy.aspx

    http://www.johnchapman.name/aspnet-proxy-page-cross-domain-requests-from-ajax-and-javascript/

    Re-Render the page

    I did not go this far but there are jscript engines out there that will look at the source and re-render the page based on the source file . but it would require hacking those jscripts.. and thats not an ideal situation for commercial entities... and some invole pure java applets or server side rendering

    http://en.wikipedia.org/wiki/Server-side_JavaScript

    http://htmlunit.sourceforge.net/ <-java not jscript

    http://maxq.tigris.org/


    EDIT 09-2013 UPDATE

    All this can do done with HTML5 sockets. But easyXDM is great fallback for non HTML5 complaint pages.

    Solution 1 Very Great Solution!

    Using easyXDM

    On your server you set up a page in the form of

    <html>
    <head>
    <script src="scripts/easyXDM.js" type="text/javascript"></script>
    <script type="text/javascript" language="javascript">
    
        var transport = new easyXDM.Socket(/** The configuration */{
        remote: "http://www.OTHERDOMAIN.com/resize_intermediate.html?url=testpages/resized_iframe_1.html",
    
        //ID of the element to attach the inline frame to
        container: "embedded",
        onMessage: function (message, origin) {
            var settings = message.split(",");
            //Use jquery on a masterpage.
            //$('iframe').height(settings[0]);
            //$('iframe').width(settings[1]);
    
            //The normal solution without jquery if not using any complex pages (default)
            this.container.getElementsByTagName("iframe")[0].style.height = settings[0];
            this.container.getElementsByTagName("iframe")[0].style.width = settings[1];
        }
    });
    
    </script>
    
    </head>
    
    <body>
        <div id="embedded"></div>
    </body>
    

    and on the callers domain they just need to add the intermiedate_frame html and easyXDM.js in the same place. Like a parent folder - then you can access relative directories or a contained folder just for you.

    OPTION 1

    If you don't want to add scripts to all pages look at option 2!

    Then they can just add a simple jscript to the end of each the pages you need the resize to occur. No need to include the easyxdm in each of these pages.

     <script type="text/javascript">
                window.onload = function(){ parent.socket.postMessage( (parseInt(document.body.clientHeight)) + "," + ( document.body.clientWidth ) );  };
            </script>
    

    I have modified the parameters it sends. If you want the width to work properly then the pages on the otherdomain need to include the width of the page in a style somwhere that look something similar to:

    <style type="text/css">
                html, body {
                    overflow: hidden;
                    margin: 0px;
                    padding: 0px;
                    background-color: rgb(75,0,85);
                    color:white;
                    width:660px
                }
                a {
                color:white;
                visited:white;
                }
            </style>
    

    This works great for me. If the width is not included then the frame behaves a bit strange and kind'of tries to guess what it should be .. and will not shrink down if you need it to.

    OPTION 2

    Modify the intermediate frame to poll for changes

    Your intermediate frame should look something like this..

        <!doctype html>
    <html>
        <head>
    
            <title>Frame</title>
            <script type="text/javascript" src="easyXDM.js">
            </script>
            <script type="text/javascript">
                var iframe;
                var socket = new easyXDM.Socket({
                    //This is a fallback- not needed in many cases
                    swf: "easyxdm.swf",
                    onReady: function(){
                        iframe = document.createElement("iframe");
                        iframe.frameBorder = 0;
                        document.body.appendChild(iframe);
                        iframe.src = "THE HOST FRAME";
                iframe.onchange = messageBack();
    
                    },
                    onMessage: function(url, origin){
                        iframe.src = url;
                    }
                });
    
                //Probe child.frame for dimensions.
                function messageBack(){
                    socket.postMessage ( iframe.contentDocument.body.clientHeight + "," + iframe.contentDocument.body.clientWidth); 
                };
    
                //Poll for changes on children every 500ms.
                setInterval("messageBack()",500); 
    
            </script>
            <style type="text/css">
                html, body {
                    overflow: hidden;
                    margin: 0px;
                    padding: 0px;
                    width: 100%;
                    height: 100%;
                }
    
                iframe {
                    width: 100%;
                    height: 100%;
                    border: 0px;
                }
            </style>
        </head>
        <body>
    
        </body>
    </html>
    

    The interval could be made more efficient to chaeck if size has change and only send if the dimensions changes isntead of posting messaging every 500ms. If you implement this check then you can change the polling as low as 50ms! have fun


    Work across browsers and is fast. Great debugging features !!

    Excellent Work to  Sean Kinsey  who made the script!!!
    

    Solution 2 (Works but not great)

    So basically if you have a mutual agreement with the other domain then you can add a library to handle sendmessage. If you do not have any access the the other domain.. Keep on looking for more hacks- because I could not find or fully justify these I found.

    So the other domain will include these in there Head tag

    <script src="scripts/jquery-1.5.2.min.js" type="text/javascript"></script>
    <script src="scripts/jquery.postmessage.min.js" type="text/javascript"></script>
    <script src="scripts/club.js" type="text/javascript"></script>
    

    In the club.js is just some custom calls i made for resize calls and contains..

     $(document).ready(function () {   
        var parent_url = decodeURIComponent( document.location.hash.replace( /^#/, '' ) ),link;
    //Add source url hash to each url to authorise callback when navigating inside the frame.Without this clicking any links will break the communication and no messages will be received
    $('a[href]').each(function(){ 
         this.href = this.href + document.location.hash ;
    });
    //Get the dimensions and send back to calling page.
    var h1 = document.body.scrollHeight;
    var w1 = document.body.scrollWidth;
    $.postMessage({ if_height: h1, if_width: w1 }, parent_url, parent );
      });
    

    And your page will do all the hard work and has a nice script...

      //This is almost like request.querystring used to get the iframe data
      function querySt(param, e) {
             gy = e.split("&");
             for (i = 0; i < gy.length; i++) {
                 ft = gy[i].split("=");
                 if (ft[0] == param) {
                     return ft[1];
                 }
             }
         }
    
    
         $(function () {
             // Keep track of the iframe dimensions.
             var if_height;
             var if_width;
             // Pass the parent page URL into the Iframe in a meaningful way (this URL could be
             // passed via query string or hard coded into the child page, it depends on your needs).
             src = 'http://www.OTHERDOAMIN.co.uk/OTHERSTARTPAGE.htm' + '#' + encodeURIComponent(document.location.href),
             // Append the Iframe into the DOM.
             iframe = $('<iframe " src="' + src + '" width="100%" height="100%" scrolling="no" frameborder="0"><\/iframe>').appendTo('#iframe');
    
             // Setup a callback to handle the dispatched MessageEvent event. In cases where
             // window.postMessage is supported, the passed event will have .data, .origin and
             // .source properties. Otherwise, this will only have the .data property.
             $.receiveMessage(function (e) {
                 // Get the height from the passsed data.
                 //var h = Number(e.data.replace(/.*if_height=(\d+)(?:&|$)/, '$1'));
                 var h = querySt("if_height", e.data);
                 var w = querySt("if_width", e.data);
    
    
                 if (!isNaN(h) && h > 0 && h !== if_height) {
                     // Height has changed, update the iframe.
                     iframe.height(if_height = h);
                 }
                 if (!isNaN(w) && w > 0 && w !== if_width) {
                     // Height has changed, update the iframe.
                     iframe.width(if_width = w);
                 }
                 //For debugging only really- can remove the next line if you want
                 $('body').prepend("Recieved" + h + "hX" + w + "w .. ");
                 // An optional origin URL (Ignored where window.postMessage is unsupported).
               //Here you must put the other domain.com name only! This is like an authentication to prevent spoofing and xss attacks! 
             }, 'http://www.OTHERDOMAIN.co.uk');
         });
    

    OPTION 3

    Their is now a small JS library for managing resizing cross-domain iFrames, it still requires the iFrame to have a bit of JavaScript in it, however, this is just 2.8k (765 bytes Gzipped) of native JS that does not have any dependancies and it doesn't do anything until called by the parent page. This means it's a nice guest on other people systems.

    This code uses mutationObserver to detect DOM changes and also looks out for resize events, so that the iFrame remains sized to the content. Works in IE8+.

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

  • Piotr Kula
    Piotr Kula about 13 years
    Yes i tried that already. It works great.. on the same domain like you said.. Tell me- how does a browser know how much 100% is when it initially calculates the height? why cant we access those values also? Chrome stores this in computed styles for every single page in the iframe even x-domain...
  • MarioRicalde
    MarioRicalde about 13 years
    Height gets calculated depending on the contents and the attributes they have. The height of an element is uncertain until the the children are rendered.
  • Piotr Kula
    Piotr Kula about 13 years
    how can i get the rendered values then? like the inspect in chrome does?
  • Sean Kinsey
    Sean Kinsey about 13 years
    -1 since this doesn't work cross-domain, which is what he asked for.
  • Quentin
    Quentin about 13 years
    You can't get the values. The inspector can because it has "browser extension" level permissions and not "web page with a different origin" level permissions.
  • Piotr Kula
    Piotr Kula about 13 years
    On the contrary my friend. I found a way to resize the iframe from a cross doimain page to the iframes docuemnt size and no scrolbars. its kinda hacky but will post later.. its just so simple its unbelievable.
  • Piotr Kula
    Piotr Kula almost 13 years
    yea... I used sendmessage jquwery plugin- i though i found another hack- false positive.. ;) thanks for the good tip!
  • Sean Kinsey
    Sean Kinsey almost 13 years
    Well, sometimes the best thing is to listen to your peers - the odds for you discovering some magic hack that no-one else has is pretty slim :)
  • Piotr Kula
    Piotr Kula almost 13 years
    Imagine if i did- by pure hit and miss - would be fantastic. But yea- slim :D Thanks
  • Sean Kinsey
    Sean Kinsey almost 13 years
    True - if nothing else, you have at least tried, experimented, and perhaps learned something new as well :)
  • Elton Morais
    Elton Morais over 12 years
    Off course, the "iframe" scroll will be, allways, of the same size of the page. So, while navigating, You just see it as it was the "page" scroll.
  • dbonneville
    dbonneville about 11 years
    This works great but not quite as much in IE. I had to send 2 variables, but IE won't accept "data" if its an array or object. However, I simply created data as data = var1 + "|" var2. On the parent, you just split the string on | and get your 2 vars back.
  • Piotr Kula
    Piotr Kula almost 11 years
    Nice one dude. Definitely something that will be used by allot of people! +1
  • Royi Namir
    Royi Namir over 10 years
    @SeanKinsey Which browsers supports the onResize ( for iframe ) ?
  • Sean Kinsey
    Sean Kinsey over 10 years
    @RoyiNamir none, unless you consider seamless iframes - benvinegar.github.io/seamless-talk/# The easyXDM example uses Cross-Domain Messaging to relay regular resize events.
  • Royi Namir
    Royi Namir over 10 years
    @SeanKinsey I meant -- A Page set a new hash value to its iframe and now it needs to resize the iframe in order to raise the onresize event ( at the iframe side). are you telling me that the event won't fire at the iframe size ? ( that's my question was about). thanks for reply. I even asked a new question about it
  • Andrew Barber
    Andrew Barber over 10 years
    I don't think you are addressing the actual question asked.
  • Piotr Kula
    Piotr Kula over 10 years
    Yea- This is pure javascript to resize an iframe on the same domain. It is much easier to use jQuery any way. This wont work cross site and there is nothing fancy in here that will allow it. And there is no such thing as php scripting?!
  • Bala Peterson
    Bala Peterson over 9 years
    what do I do if I do not have access to the remote domain? Thanks
  • David Bradshaw
    David Bradshaw about 9 years
    @BalaPeterson, if you can't access the remote domain, then you can not do this due to the security restrictions in the browser.