Interacting with a different origin iFrame using Javascript is not possible. To retrieve its size, the only method available is to utilize window.postMessage
with the targetOrigin
set to your domain or the wildcard *
from the source iFrame. Although it's feasible to proxy the content of other origin sites and employ srcdoc
, this approach is considered a workaround and may not function effectively with Single Page Applications (SPAs) and several other dynamically generated pages.
Size of Same Origin iFrames
Consider having two same-origin iFrames, one with a short height and fixed width:
<!-- iframe-short.html -->
<head>
<style type="text/css">
html, body { margin: 0 }
body {
width: 300px;
}
</style>
</head>
<body>
<div>This is an iFrame</div>
<span id="val">(val)</span>
</body>
and another iFrame with a long height:
<!-- iframe-long.html -->
<head>
<style type="text/css">
html, body { margin: 0 }
#expander {
height: 1200px;
}
</style>
</head>
<body>
<div>This is a long height iFrame Start</div>
<span id="val">(val)</span>
<div id="expander"></div>
<div>This is a long height iFrame End</div>
<span id="val">(val)</span>
</body>
The size of iFrames during the load
event can be obtained using iframe.contentWindow.document
. Subsequently, this information will be sent to the parent window utilizing postMessage
:
<div>
<iframe id="iframe-local" src="iframe-short.html"></iframe>
</div>
<div>
<iframe id="iframe-long" src="iframe-long.html"></iframe>
</div>
<script>
function iframeLoad() {
window.top.postMessage({
iframeWidth: this.contentWindow.document.body.scrollWidth,
iframeHeight: this.contentWindow.document.body.scrollHeight,
params: {
id: this.getAttribute('id')
}
});
}
window.addEventListener('message', ({
data: {
iframeWidth,
iframeHeight,
params: {
id
} = {}
}
}) => {
// Considering a "border-width: 3px" for all iframes, we add 6 pixels
if (iframeWidth) {
document.getElementById(id).style.width = `${iframeWidth + 6}px`;
}
if (iframeHeight) {
document.getElementById(id).style.height = `${iframeHeight + 6}px`;
}
}, false);
document.getElementById('iframe-local').addEventListener('load', iframeLoad);
document.getElementById('iframe-long').addEventListener('load', iframeLoad);
</script>
Accurate width and height values are acquired for both iFrames. For demonstration purposes, you can view this online here along with the screenshot here.
Hacks for Different Origin iFrame Sizing (Not Recommended)
The following method constitutes a hack and should only be employed when absolutely necessary, as it does have limitations. This technique retrieves the HTML source code of a page through a proxy in order to circumvent Cross-Origin Resource Sharing (CORS) policies. cors-anywhere
serves as a straightforward CORS proxy server, offering an online demo at
https://cors-anywhere.herokuapp.com
. After acquiring the HTML source, custom JS code is injected to implement
postMessage
for transmitting iFrame dimensions to the parent document. Furthermore, it manages iFrame resizing events (
combined with iFrame width: 100%
) by returning the iFrame size to the parent.
patchIframeHtml
:
This function serves to patch the iFrame HTML code and insert customized Javascript that utilizes postMessage
to send iFrame dimensions to the parent upon load
and resize
events. Should there be an origin
parameter value provided, an HTML <base/>
element containing that origin URL will be prepended to the head portion, enabling proper URI fetches within the iFrame.
function patchIframeHtml(html, origin, params = {}) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const script = doc.createElement('script');
script.textContent = `
window.addEventListener('load', () => {
document.body.style.height = 'auto';
document.body.style.overflowY = 'auto';
poseResizeMessage();
});
window.addEventListener('resize', poseResizeMessage);
function poseResizeMessage() {
window.top.postMessage({
iframeHeight: document.body.scrollHeight,
params: JSON.parse(decodeURIComponent('${encodeURIComponent(JSON.stringify(params))}'))
}, '*');
}
`;
doc.body.appendChild(script);
if (origin) {
const base = doc.createElement('base');
base.setAttribute('href', origin);
doc.head.prepend(base);
}
return doc.documentElement.outerHTML;
}
getIframeHtml
:
This function facilitates retrieval of a page's HTML content, bypassing CORS restrictions by utilizing a proxy when specified by the useProxy
parameter flag. Additional parameters can also be included for transmission via postMessage
while sending size data.
function getIframeHtml(url, useProxy = false, params = {}) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
let origin = useProxy && (new URL(url)).origin;
const patchedHtml = patchIframeHtml(xhr.responseText, origin, params);
resolve(patchedHtml);
}
}
xhr.open('GET', useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url, true);
xhr.send();
});
}
The message event handler function remains unchanged from the one in the section titled "Same origin iFrame size".
An instance demonstrating loading a cross-origin domain within an iFrame with injected custom JS code:
<!-- The iFrame requires a 100% width for the resize event functionality -->
<iframe id="iframe-cross" style="width: 100%"></iframe>
<script>
window.addEventListener('DOMContentLoaded', async () => {
const crossDomainHtml = await getIframeHtml(
'https://en.wikipedia.org/wiki/HTML', true /* useProxy */, { id: 'iframe-cross' }
);
document.getElementById('iframe-cross').setAttribute('srcdoc', crossDomainHtml);
});
</script>
Conclusively, the iFrame adjusts itself to accommodate the entire content without any vertical scrolling even when overflow-y: auto
is used for the iFrame body (ideally, this property should be set to overflow-y: hidden
to prevent scrollbar flickering on resize). The functionality can be observed online here.
It must be reemphasized that this method is a hack, hence it should be avoided; accessing the document of a Cross-Origin iFrame or injecting elements into it is not permissible.