cross-domain iframe resize

2019-01-01 14:44发布

问题:

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

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

回答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..



回答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



回答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
        );
    }
});


回答4:

Instead of use scroll=no on the iframe, I change it to \"auto\". Then, I get the size of the actual window

$(window).height();

and use that as the iframe height attribute.

Well, the result is...

We will never have the \"page\" scroll, only the \"iframe\" scroll. When You navigate, doesn\'t matter who is the scroll, but the important is that there\'s only 1.

Well, there\'s the problem of the user simply resize the window while he\'s navigating. To solve that, I use:

setInterval(getDocHeight, 1);

Did You think that that solution will cause any bugs? It\'s working for me, and on the iFrame I had dynamic contect generated by php. I\'m afraid of future bugs with that...



回答5:

Nowadays there is only 4 solutions I know:

  • postMessage. Some browsers are not supported of course.
  • window.location.hash manipulations. I think it is solution #2 here.
  • easyXDM
  • window.name Transport

Only the third one can resolve many problems. For example you can create responsive iFrame; close it from inside or you can communicate with it. But to do that you need iFrame in Iframe and the \"third party cookies\" workaround (optional).

I\'ve created an article about it with example: Event-driven cross-domain iFrame



回答6:

Have you looked into the \'object-fit\' HTML5 attributes? Scales video/images to the iframe, rather than scaling the iframe (nice if you grab a medium-sized image that ends up being 5,000px in width). The \"fit\" option (others are \"cover\" and \"fill\") uses a sort-of-letterbox approach to fit the source in while preserving the aspect ratios. For viewing by the HTML5-less out there, it looks like there a whoooole lot of polyfills available. This one is great, but a bug on Edge\'s end has kept it incompatible with Microsoft\'s New Nightmare for about a year, now: https://github.com/anselmh/object-fit

EDIT: To get around cross domain issues, you can always just do the work in a Chrome Extension Content Script, since it thinks it\'s part of the page you\'re sticking your iframe on.



回答7:

HTTPS another link get height to iframe autoheight

https://-a.com content:

 <!DOCTYPE html>
    <html>
    <head>
        <title>Test Page</title>
    </head>
    <body>
        Test Page:
        <hr/>
        <iframe id=\"iframe\" src=\"https://-b.com\" style=\"width:100%;min-height:600px;border:none;\" scrolling=\"no\" ></iframe>
        <script>
        // browser compatibility: get method for event 
        // addEventListener(FF, Webkit, Opera, IE9+) and attachEvent(IE5-8)
        var myEventMethod = 
            window.addEventListener ? \"addEventListener\" : \"attachEvent\";
        // create event listener
        var myEventListener = window[myEventMethod];
        // browser compatibility: attach event uses onmessage
        var myEventMessage = 
            myEventMethod == \"attachEvent\" ? \"onmessage\" : \"message\";
        // register callback function on incoming message
        myEventListener(myEventMessage, function (e) {
            // we will get a string (better browser support) and validate
            // if it is an int - set the height of the iframe #my-iframe-id
            if (e.data === parseInt(e.data)) 
                document.getElementById(\'iframe\').height = e.data + \"px\";
        }, false);
        </script>
    </body>
    </html>

https://-b.com iframe content:

<!DOCTYPE html>
<html>
<head>
    <title>Test Iframe Content</title>
    <script type=\"text/javascript\">
    // all content including images has been loaded
    window.onload = function() {
        // post our message to the parent
        window.parent.postMessage(
            // get height of the content
            document.body.scrollHeight
            // set target domain
            ,\"*\"
        )
    };
</script>
</head>
<body>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>1
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>2
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>3
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>4
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>5
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>6
</body>
</html>

Work Table:

xcom http > ycom https WORK

xcom https > ycom https WORK

xcom http > ycom http WORK

xcom https > ycom http WORK

\"Test



回答8:

Are you looking to find the height of the page contained within the iframe? I got some javascript working which checks the height of the iframe content then sets the iframe\'s height to the height of the content.

var Height = document.getElementById(\'iFrameId\').contentWindow.document.body.scrollHeight;

document.getElementById(\'iFrameId\').height = Height;

However, this only works if the page you are showing in the iframe is on the same domain. If it\'s not you cannot access the required information. Hence, the access denied error.



回答9:

To resize an iframe, here\'s a simple script:

this goes in the head: (this was written for a php script, for html, change the \' to \" and the \\\" to \'...

<script type=\'text/javascript\'>
<!--
function resizeIframe(id){
/*
this.obj=obj
//this.obj.width=null
//this.obj.width=window.frames[\\\"sizeframe1\\\"].document.body.scrollWidth
this.obj.style.height=\\\"\\\" // for Firefox and Opera
setTimeout(\\\"this.obj.style.height=this.obj.contentWindow.document.body.scrollHeight+(notIE?heightOffset:0)\\\",10) // setTimeout required for Opera
*/

el=document.getElementById(id)
el.style.height=\'200px\' // for Firefox and Opera
setTimeout(\'el.style.height=el.contentWindow.document.body.scrollHeight+\\\"px\\\"\',1) // setTimeout required for Opera
}

// -->
</script>\"

end of head

this goes in the body (remember, this was written for a php script, for html change all the \' to \" and the \\\" to \'...)

<iframe onload=\'resizeIframe(this.id)\' allowTransparency=\\\"true\\\" name=\'ccpaymentwindow\' id=\'sizeframe1\' marginwidth=\'1\' marginheight=\'1\' height=\'700\' width=\'690\' border=\'0\' frameborder=\'1\' scrolling=\'no\' src=\'ccmslink.php?variable1=\" . $variable1 . \"\'></iframe>

bonus: there\'s some hints above. As it is set for php scripting, you can do a lot with it...to learn more, do more...

the key to this is \"sizeframe1\" .... for multiple \"resizers\" on the same page, copy the same script but change the id in iframe and the name in the script in the head, and viola! you have multiple resizers on the same page...it works very well!

have phun.