No es posible interactuar con un iFrame de origen diferente usando Javascript para obtener el tamaño del mismo; la única forma de hacerlo es mediante el uso window.postMessage
del targetOrigin
conjunto a su dominio o el comodín *
de la fuente iFrame. Puede representar los contenidos de los diferentes sitios de origen y su uso srcdoc
, pero eso se considera un hack y no funcionará con SPA y muchas otras páginas más dinámicas.
Mismo tamaño de iFrame de origen
Supongamos que tenemos dos iFrames del mismo origen, uno de corta altura y ancho fijo:
<!-- 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>
y un iFrame de larga altura:
<!-- 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>
Podemos obtener el tamaño de iFrame en el load
evento usando iframe.contentWindow.document
que enviaremos a la ventana principal usando 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
} = {}
}
}) => {
// We add 6 pixels because we have "border-width: 3px" for all the iframes
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>
Obtendremos el ancho y la altura adecuados para ambos iFrames; Puede consultarlo en línea aquí y ver la captura de pantalla aquí .
Hack de diferente tamaño de iFrame ( no recomendado )
El método descrito aquí es un truco y debe usarse si es absolutamente necesario y no hay otra forma; no funcionará para la mayoría de las páginas dinámicas generadas y SPA. El método obtiene el código fuente HTML de la página utilizando un proxy para omitir la política CORS ( cors-anywhere
es una manera fácil de crear un servidor proxy CORS simple y tiene una demostración en líneahttps://cors-anywhere.herokuapp.com
) y luego inyecta el código JS a ese HTML para usarlo postMessage
y enviar el tamaño del iFrame al documento padre. Incluso maneja el evento iFrame resize
( combinado con iFramewidth: 100%
) y publica el tamaño del iFrame en el padre.
patchIframeHtml
:
Una función para parchear el código HTML de iFrame e inyectar Javascript personalizado que se usará postMessage
para enviar el tamaño de iFrame al padre una load
y otra vez resize
. Si hay un valor para el origin
parámetro, entonces se <base/>
agregará un elemento HTML al encabezado usando esa URL de origen, por lo tanto, los URI de HTML como /some/resource/file.ext
serán recuperados correctamente por la URL de origen dentro del iFrame.
function patchIframeHtml(html, origin, params = {}) {
// Create a DOM parser
const parser = new DOMParser();
// Create a document parsing the HTML as "text/html"
const doc = parser.parseFromString(html, 'text/html');
// Create the script element that will be injected to the iFrame
const script = doc.createElement('script');
// Set the script code
script.textContent = `
window.addEventListener('load', () => {
// Set iFrame document "height: auto" and "overlow-y: auto",
// so to get auto height. We set "overlow-y: auto" for demontration
// and in usage it should be "overlow-y: hidden"
document.body.style.height = 'auto';
document.body.style.overflowY = 'auto';
poseResizeMessage();
});
window.addEventListener('resize', poseResizeMessage);
function poseResizeMessage() {
window.top.postMessage({
// iframeWidth: document.body.scrollWidth,
iframeHeight: document.body.scrollHeight,
// pass the params as encoded URI JSON string
// and decode them back inside iFrame
params: JSON.parse(decodeURIComponent('${encodeURIComponent(JSON.stringify(params))}'))
}, '*');
}
`;
// Append the custom script element to the iFrame body
doc.body.appendChild(script);
// If we have an origin URL,
// create a base tag using that origin
// and prepend it to the head
if (origin) {
const base = doc.createElement('base');
base.setAttribute('href', origin);
doc.head.prepend(base);
}
// Return the document altered HTML that contains the injected script
return doc.documentElement.outerHTML;
}
getIframeHtml
:
Una función para obtener una página HTML sin pasar por CORS usando un proxy si useProxy
se establece param. Puede haber parámetros adicionales que se pasarán postMessage
al enviar datos de tamaño.
function getIframeHtml(url, useProxy = false, params = {}) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
// If we use a proxy,
// set the origin so it will be placed on a base tag inside iFrame head
let origin = useProxy && (new URL(url)).origin;
const patchedHtml = patchIframeHtml(xhr.responseText, origin, params);
resolve(patchedHtml);
}
}
// Use cors-anywhere proxy if useProxy is set
xhr.open('GET', useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url, true);
xhr.send();
});
}
La función de controlador de eventos de mensaje es exactamente la misma que en "Mismo tamaño de iFrame de origen" .
Ahora podemos cargar un dominio de origen cruzado dentro de un iFrame con nuestro código JS personalizado inyectado:
<!-- It's important that the iFrame must have a 100% width
for the resize event to work -->
<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' }
);
// We use srcdoc attribute to set the iFrame HTML instead of a src URL
document.getElementById('iframe-cross').setAttribute('srcdoc', crossDomainHtml);
});
</script>
Y conseguiremos que el iFrame se ajuste a su contenido a toda su altura sin ningún desplazamiento vertical, incluso si se usa overflow-y: auto
para el cuerpo del iFrame ( debe ser overflow-y: hidden
así para que no tengamos parpadeo de la barra de desplazamiento al cambiar el tamaño ).
Puedes consultarlo en línea aquí .
Una vez más, para notar que esto es un truco y debe evitarse ; que no se puede acceder de origen cruzado documento marco flotante ni inyectar cualquier tipo de cosas.