Making IFrames Responsive
While creating the embed for caniuse, one part I found quite challenging was making the iframe fully responsive. When the iframe is resized, the content within it shifts, and the height that the <iframe>
element itself should be, changes. However, because of the Content Security Same-origin Policy, there is no way to access the height of the content within the iframe from the document hosting the iframe.
With most embedded content, for example a Youtube Video, we can workaround this because, even though the height does change, the aspect ratio stays the same. So, we can use CSS to determine what the height of the iframe should be at any given width. With this caniuse embed, however, there is no standard size so I needed to have access to the actual height of the content within the iframe to determine what the height of the <iframe>
element should be.
I spent ages looking around for how I could do this and came up short. The solution I eventually came up with was to use the postMessage API to communicate between a script in the embedded document, and a script in the hosting document.
Although this method is limited in that requires that we have access to both documents, I thought it would still be useful to share just in case someone else is in the same situation. So here is how it works.
1. Determine the height of the iframe content on page load #
Firstly, there are two scripts involved here -
iframe.js
- the script associated with the iframe documentparent.js
- the script associated with the document loading the iframe
In iframe.js
, we have to determine the height of the document before we can pass it to the parent document. We do this by getting the scrollHeight
of the main element (#main-element
) within the document.
var documentHeight = document.getElementById(‘main-element').scrollHeight;
We use the height of the main container element instead of just the body
because using the body element will cause an infinite loop when we repeat this on the resize of the window (See step 4).
2. Pass the height to the hosting document #
Once we have the height, we can use the postMessage API to send the message to parent.js
.
// Add some unique identifier to the string being passed
var message = 'documentHeight:'+documentHeight;
// Pass message to (any) parent document
parent.postMessage(message, "*");
Then, in parent.js, we add an event listener for the message. The method I’m using here is based off an article from David Walsh.
// Setup event listener
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen for event
eventer(messageEvent,function(e) {
// Check that message being passed is the documentHeight
if ( (typeof e.data === 'string') && (e.data.indexOf('documentHeight:') > -1) ) {
// Split string from identifier
var height = e.data.split('documentHeight:')[1];
// do stuff with the height
}
},false);
3. Resize the <iframe>
element's height appropriately #
When we receive the new height from the iframe.js
, we can change the height of the <iframe>
element in the parent document.
var height = e.data.split('documentHeight:')[1],
height = parseInt(height) + 30; // add a bit of extra space as buffer
// Change height of <iframe> element
document.getElementById('myIframe').height = height + 'px';
4. Repeat on resize of the iframe #
Back in iframe.js
, we can send the new height to parent.js
everytime the height of the document changes. Using window.onresize
, we can check if the documentHeight
has changed significantly (in this case by at least 10px), then send the new height to parent.js
.
// On resize of the window, recalculate the height of the main element,
// and pass to the parent document again
window.onresize = function(event) {
var newDocumentHeight = document.getElementById(‘main-element').scrollHeight;
var heightDiff = documentHeight - newDocumentHeight;
// If difference between current height and new height is more than 10px
if ( heightDiff > 10 | heightDiff < -10 ) {
documentHeight = newDocumentHeight;
message = 'documentHeight:'+documentHeight;
parent.postMessage(message,"*");
}
}
Once the message is posted to parent.js
, the <iframe>
element's height will be updated.
The reason we don't use the body
element to check the height of the iframe content is because, when we resize the <iframe>
element in the parent document, that itself will trigger a change in the height of the body
element in the iframe document, causing an infinite loop.
You can check out a demo of this at demo.bitsofco.de/responsive-iframe.