<iframe>’s which display content from different domains have security measures in place to prevent all sorts of stuff. For example, you can’t have JavaScript access anything inside it. It can be very frustrating, for example, if you just want to do something normal and white-hat like adjust the height of the iframe to fit the content inside it. These security measures are in place to prevent all the black-hat kind of things you could do if you did have JavaScript access to the innards of an iframe.
I’ve literally tried to work on different solutions for this for years and always came up short. I recently came across a solution from Kazi Manzur Rashid (about two years old now) that looks pretty solid so I thought I’d try it out. The results are the closest I’ve been able to come yet:
Warning: the demo kinda freaks out WebKit browsers like Safari and Chrome, see issues below.
To those who have come before…
To do this with an iframe with source content on the same domain, you can do this. Same-domain iframes aren’t subject to the same restrictions so it’s far easier.
Adam Fortuna explored some options using kind of a man-in-the-middle idea. This may have been inspired by a technique by John McKerrell.
The following technique doesn’t require the middle man thing though, which is why it’s closer to ideal.
Prereqs
This solution presupposes that you have control over both the hosting site and the source site. You’ll need to run JavaScript on both ends. So this isn’t going to work for an iframe of google.com.
The Big Idea
The work-around is using hash tags in the URL to relay information back and forth. This circumvents the security restrictions. It is unlikely that this will ever break, so it’s not really a “hack”. You can’t really do anything malicious with just a hash tag. In our case we’re just reading in that information and using it to do the resizing.
The HOST Domain
Actually has the iframe on it:
<iframe id="frame-one" scrolling="no" frameborder="0" src="http://digwp.com/examples/iFrameSource/source.html" onload="FrameManager.registerFrame(this)"></iframe>
The iframe has an onload event on it, which calls a function from the FrameManager class, which we’ll need to call in the <head>:
<script type="text/javascript" src="js/FrameManager.js"></script>
And here is the magical FrameManager class:
var FrameManager = {
currentFrameId : '',
currentFrameHeight : 0,
lastFrameId : '',
lastFrameHeight : 0,
resizeTimerId : null,
init: function() {
if (FrameManager.resizeTimerId == null) {
FrameManager.resizeTimerId = window.setInterval(FrameManager.resizeFrames, 500);
}
},
resizeFrames: function() {
FrameManager.retrieveFrameIdAndHeight();
if ((FrameManager.currentFrameId != FrameManager.lastFrameId) || (FrameManager.currentFrameHeight != FrameManager.lastFrameHeight)) {
var iframe = document.getElementById(FrameManager.currentFrameId.toString());
if (iframe == null) return;
iframe.style.height = FrameManager.currentFrameHeight.toString() + "px";
FrameManager.lastFrameId = FrameManager.currentFrameId;
FrameManager.lastFrameHeight = FrameManager.currentFrameHeight;
window.location.hash = '';
}
},
retrieveFrameIdAndHeight: function() {
if (window.location.hash.length == 0) return;
var hashValue = window.location.hash.substring(1);
if ((hashValue == null) || (hashValue.length == 0)) return;
var pairs = hashValue.split('&');
if ((pairs != null) && (pairs.length > 0)) {
for(var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
if ((pair != null) && (pair.length > 0)) {
if (pair[0] == 'frameId') {
if ((pair[1] != null) && (pair[1].length > 0)) {
FrameManager.currentFrameId = pair[1];
}
} else if (pair[0] == 'height') {
var height = parseInt(pair[1]);
if (!isNaN(height)) {
FrameManager.currentFrameHeight = height;
FrameManager.currentFrameHeight += 15;
}
}
}
}
}
},
registerFrame: function(frame) {
var currentLocation = location.href;
var hashIndex = currentLocation.indexOf('#');
if (hashIndex > -1) {
currentLocation = currentLocation.substring(0, hashIndex);
}
frame.contentWindow.location = frame.src + '?frameId=' + frame.id + '#' + currentLocation;
}
};
window.setTimeout(FrameManager.init, 300);
The SOURCE site
The source content could pretty much be anything, located on a different server. Perhaps:
<body>
<div id="content">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce in tortor sit amet sem luctus ornare. Nam sed augue id erat commodo gravida. Nulla in pede. Nunc sed elit non pede aliquam eleifend. Cras varius. Sed non lorem eget ipsum accumsan suscipit. Donec bibendum enim. Phasellus a ligula. Fusce turpis diam, ultricies at, ullamcorper a, consectetuer et, mauris. Pellentesque neque felis, scelerisque non, vestibulum at, luctus quis, velit. Quisque sit amet mi sed sem facilisis ornare. In leo ante, hendrerit nec, lobortis eget, feugiat ac, orci.
</div>
</body>
The most important thing we do on the source site is run some JavaScript to “publish” the height of itself. In my demo, I’m also throwing some jQuery in there to do some font-size animation so that the source content grows taller and shorter.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2"></script>
<script type="text/javascript" src="frame.js"></script>
<script type="text/javascript">
window.onload = function(event) {
window.setInterval(publishHeight, 300);
}
$(function() {
var $content = $("#content");
function toggleFontSize() {
if ($content.css("font-size") == "22px") {
$("#content").animate({
fontSize: "15px"
});
} else {
$("#content").animate({
fontSize: "22px"
});
}
}
var int = setInterval(toggleFontSize, 5000);
});
</script>
So we are calling publishHeight() every 300 milliseconds. Here is that function, and it’s rag-tag gang of fellow supporting functions from the frame.js file.
function publishHeight() {
if (window.location.hash.length == 0) return;
var frameId = getFrameId();
if (frameId == '') return;
var actualHeight = getBodyHeight();
var currentHeight = getViewPortHeight();
if (Math.abs(actualHeight - currentHeight) > 15) {
var hostUrl = window.location.hash.substring(1);
hostUrl += "#";
hostUrl += 'frameId=' + frameId;
hostUrl += '&';
hostUrl += 'height=' + actualHeight.toString();
window.top.location = hostUrl;
}
}
function getFrameId() {
var qs = parseQueryString(window.location.href);
var frameId = qs["frameId"];
var hashIndex = frameId.indexOf('#');
if (hashIndex > -1) {
frameId = frameId.substring(0, hashIndex);
}
return frameId;
}
function getBodyHeight() {
var height,
scrollHeight,
offsetHeight;
if (document.height) {
height = document.height;
} else if (document.body) {
if (document.body.scrollHeight) {
height = scrollHeight = document.body.scrollHeight;
}
if (document.body.offsetHeight) {
height = offsetHeight = document.body.offsetHeight;
}
if (scrollHeight && offsetHeight) {
height = Math.max(scrollHeight, offsetHeight);
}
}
return height;
}
function getViewPortHeight() {
var height = 0;
if (window.innerHeight) {
height = window.innerHeight - 18;
} else if ((document.documentElement) && (document.documentElement.clientHeight)) {
height = document.documentElement.clientHeight;
} else if ((document.body) && (document.body.clientHeight)) {
height = document.body.clientHeight;
}
return height;
}
function parseQueryString(url) {
url = new String(url);
var queryStringValues = new Object(),
querystring = url.substring((url.indexOf('?') + 1), url.length),
querystringSplit = querystring.split('&');
for (i = 0; i < querystringSplit.length; i++) {
var pair = querystringSplit[i].split('='),
name = pair[0],
value = pair[1];
queryStringValues[name] = value;
}
return queryStringValues;
}
Issues
- Refresh happy in WebKit. Apparently it used to be Firefox that got all refresh happy, but apparently with the latest version it is flip flopped. Watch out of visiting the demo in Safari or Chrome, it’s a little choppy. If anyone has any ideas here, this is probably the biggest problem.
- Messes up back button. Because of all the hash tags flying around on the host page, it may screw up the back button functionality on that page.
- Intervals, intervals, intervals. There are a lot of intervals flying around here which are nearly always hacky-red-flags. The quicker the intervals, the smoother but more resource intensive. The slower, the more choppy but easier. Either way, sucky.
- Limit of information sent via hash. If you were thinking about using this technique to send other information, because it happens via URL, you are limited by the amount of information that can pass. Presumably the same as a GET request… around 1k.
The Holy Grail
I think the reason I’m so obsessed with this because Wufoo forms seem to handle this so perfectly. Wufoo forms used to only be embeddable with iframes. I always had to set the height of them literally almost 50% taller than the content itself to accommodate for the innards growing when the form was submitted with errors (the error messaging expanded the height). If I didn’t, the submit button would get cut off making the form un-submittable.
Wufoo now has a JavaScript embed option, but ultimately the form still comes in via iframe. However they do it, the iframe can magically resize itself as needed. I have no idea how it’s done, but I imagine it something somewhat similar to what we are doing here. Because Wufoo has access to both the host page and the source page. My best guess is that the JavaScript on the host page can send requests back to the source page which can somehow accurately tell the host page what height it should be.
Got better?
It’s a lot of code, but hey, it works (again, thanks for Kazi for the smarts). Got better? Please, share.
Update: David Bradshaw released iframe-resizer:
A simple library for cross domain sizing iFrames to content with support for window resizing and multiple iFrames.
Which works in any browser that supports postMessage (IE 8+)
Instead of saving your data to the #hash tag you could save it to the
iframeelment.name
and inside your iframe window.name
Forgot to mention the window.name has limit of like 2 / 4mb for IE.. and 64mb for firefox.
Hey Chris, I worked on a very similar, but more flexible method for cross-domain Iframe resizing last summer.
The benefit of my approach is that a) it’s more general and can be used for things other than Iframe resizing, b) frame communication works 2-way, and c) it utilizes the new standard window.postMessage event when available.
Feel free to take a look:
http://benalman.com/projects/jquery-postmessage-plugin/
– Ben
really nice plugin, i had no idea there was already something like that out there… great work, thanks!
This is kick-ass Ben. This is what I was trying to do and continually failing =)
When HTML5 is implemented, postMessage() will certainly makes this extremely easier.
Interesting concept! Once concern I have with the demo, is the fact that your back button is screwed. I just had to backout like crazy from the demo.
Ditto. Link back to the article from the demo would be a good idea.
what about this starting at 5:40, really awesome stuff, all of it. :D
Why would we be using an iFrame for this? Use a standards driven object tag, serve iFrame for ie6 if you must… but using an iFrame is not a supported method. Example (see conditional comments in source for iFrame method):
http://bentlyreserve.com/destination.php
Seriously ya’ll, this is a bit dated.
I honestly had no idea that the <object> tag could be used that way. I gotta imagine the same security restrictions are in play though… I’ll have to do some testing.
It does, although I believe your workaround should have the same effect.
The issue is an iFrame isn’t valid strict, and the Object tag was always meant for this purpose.
But, you might have an easier way around some of this now. iFrame is still frowned upon, but is making a comeback in html5 with the ‘seamless’ and ‘sandbox’ attributes:
http://www.w3schools.com/html5/tag_iframe.asp
You’ll notice in that sandbox call, you can ‘allow-scripts’ as an attribute. Issue sovled, eh:)? Testing shows this works in Chrome and Firefox, but not IE yet, of course.
…and to note, I’m not testing a resize, though, I’m just testing a validation javascript in html5 through an iframe
Nice write up, interesting to see how HTML will handle those security issues with the tag.
Wow! Nice tool for developers. Thanks!
@Chris – Hmmm.. I wondering is possible to
change CSS IFRAME DOM ELEMENTS (from other location) via jQuery ? (HTML5) Thanks for u response.
Best regards form Poland
Paweł P.
I’d like to know generally what people’s views are on using the element regarding best practice.
I know that they invalidate your code if using XHTML Strict, but they are allowed with XHTML Transitional.
Yet a lot of embed code provided by big players (Google…YouTube…) uses iFrame, and HTML5 seems not only to continue supporting it but gives it new attributes…
Hi Chris,
One thing to remember is that iframes are not just for displaying documents that are external to the containing page.
What you can do (and perhaps Wufoo does this, though I haven’t checked) is use JavaScript in the containing page to create a blank iframe element, and write to the document within the iframe, on-the-fly. When you do that, the iframe document is considered to be on the same domain, and you can do what you like to both the iframe element and the iframe’s document.
This way, you can append what you like to the iframe document’s body, then measure its width and height, and then assign that width and height to the iframe element.
So, problem solved! Well, yes, except the browser API for all this is a little verbose, and there are some weird inconsistencies between browsers. Which is why I started building a jQuery plugin, called “AppleOfMyIframe”, to handle exactly this kind of thing. I’ve set it up so that the iframe resizes itself automatically when new content is added and removed.
Feel free to check it out / break it / fix it / etc:
github.com/premasagar/appleofmyiframe
Call it like this:
$.iframe('Lorem ipsum').appendTo('body');
(I’m currently documenting the various methods available, in the project wiki).
Prem
In my comment above, the code block got parsed by the blog software. It should have said:
$.iframe('<div>Lorem ipsum</div>).appendTo('body');
Hi, great example!
But I’m going crazy trying to run in IE6. The page is reloded again and again… ¿Is there a problem with the hash attribute maybe?
hi,
nice article, really helpful.
i used, it works very well but i am facing one problem. when i click the link inside iframe, it refreses and bring me back to the same page. do we have any solution, where we can do this without using setInterval() function.
thanks
I am facing the same problem Uttam. You got any idea to fix it?
Ohmygoodnessthankyou.
Saved my kiester with a client who was demanding that an HTML file communicate with a .net page that got it’s information from a ColdFusion site.
Great solution!
Hello, I got an error while trying your code like “TypeError: frameId is undefined”..?? Can anyone please help me to fix this issue?? Many thanks in advance.
Wufoo probably use message events to fire information to the parent page from the child. That’s how we’ve done it with similar implementations. It saves on messy hash tags and crazy intervals yet allows for a smooth seamless implementation. Take a look into it! :)
JB
Hello,
Thanks for the script, I fixed the chrome issue in this line:
if (Math.abs(actualHeight – currentHeight) > 15) {
I puted 20 instead 15, and it seems to be working just fine.
José Tomás Sebastião
Will any of these options work if you DO NOT have the ability to edit the child page? We have a web-service we’re attempting to use, but all the “iframe jQuery resizers” i can find all seem to require the ability to modify the child page elements. The child page in our example is a 3rd-party service, so we cannot modify.
Your insights are appreciated!