Il n'est pas possible d'interagir avec une iFrame d'origine différente en utilisant Javascript afin d'en obtenir la taille; la seule façon de le faire est d'utiliser window.postMessage
avec l' targetOrigin
ensemble de votre domaine ou le caractère générique *
de la source iFrame. Vous pouvez proxy le contenu des différents sites d'origine et utiliser srcdoc
, mais cela est considéré comme un hack et cela ne fonctionnera pas avec les SPA et de nombreuses autres pages plus dynamiques.
Même taille iFrame d'origine
Supposons que nous ayons deux mêmes iFrames d'origine, une de courte hauteur et de largeur fixe:
<!-- 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>
et un iFrame de grande hauteur:
<!-- 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>
Nous pouvons obtenir la taille d'iFrame sur l' load
événement en utilisant ce iframe.contentWindow.document
que nous enverrons à la fenêtre parent en utilisant 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>
Nous obtiendrons la largeur et la hauteur appropriées pour les deux iFrames; vous pouvez le vérifier en ligne ici et voir la capture d'écran ici .
Taille différente iFrame origine bidouille ( non recommandé )
La méthode décrite ici est un hack et elle doit être utilisée si c'est absolument nécessaire et qu'il n'y a pas d'autre moyen de contourner; cela ne fonctionnera pas pour la plupart des pages et des SPA générés dynamiquement. La méthode récupère le code source HTML de la page à l'aide d'un proxy pour contourner la stratégie CORS ( cors-anywhere
est un moyen facile de créer un serveur proxy CORS simple et il a une démo en lignehttps://cors-anywhere.herokuapp.com
), puis il injecte du code JS dans ce HTML pour utiliser postMessage
et envoyer la taille du iFrame vers le document parent. Il gère même l' événement iFrame resize
( combiné avec iFramewidth: 100%
) et publie la taille de l'iFrame au parent.
patchIframeHtml
:
Une fonction pour patcher le code HTML iFrame et injecter du Javascript personnalisé qui utilisera postMessage
pour envoyer la taille iFrame au parent encore load
et encore resize
. S'il y a une valeur pour le origin
paramètre, alors un <base/>
élément HTML sera ajouté à la tête en utilisant cette URL d'origine, ainsi, les URI HTML comme /some/resource/file.ext
seront récupérés correctement par l'URL d'origine à l'intérieur de l'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
:
Une fonction pour obtenir une page HTML contournant le CORS à l'aide d'un proxy si useProxy
param est défini. Il peut y avoir des paramètres supplémentaires qui seront transmis à postMessage
lors de l'envoi des données de taille.
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 fonction de gestionnaire d'événements de message est exactement la même que dans "Taille d'origine iFrame" .
Nous pouvons maintenant charger un domaine d'origine croisée à l'intérieur d'un iFrame avec notre code JS personnalisé injecté:
<!-- 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>
Et nous obtiendrons l'iFrame à la taille de son contenu sur toute sa hauteur sans aucun défilement vertical, même en utilisant overflow-y: auto
le corps de l'iFrame ( il devrait en être overflow-y: hidden
ainsi afin que la barre de défilement ne scintille pas lors du redimensionnement ).
Vous pouvez le vérifier en ligne ici .
Encore une fois pour remarquer que c'est un hack et qu'il faut l'éviter ; nous ne pouvons pas accéder au document Cross-Origin iFrame ni injecter aucune sorte de choses.