Comment rendre un iframe réactif sans hypothèse de rapport d'aspect?

14

Comment rendre un iframe réactif, sans assumer un rapport d'aspect? Par exemple, le contenu peut avoir une largeur ou une hauteur inconnue avant le rendu.

Remarque, vous pouvez utiliser Javascript.

Exemple:

<div id="iframe-container">
    <iframe/>
</div>

Dimensionnez-le de telle iframe-containersorte que son contenu rentre à peine à l'intérieur sans espace supplémentaire, en d'autres termes, il y a suffisamment d'espace pour le contenu afin qu'il puisse être affiché sans défilement, mais sans excès d'espace. Le conteneur enveloppe parfaitement l'iframe.

Cela montre comment rendre un iframe réactif, en supposant que le rapport d'aspect du contenu est de 16: 9. Mais dans cette question, le rapport d'aspect est variable.

ferit
la source
1
Permettez-moi de clarifier avec un montage @TravisJ
ferit le
2
Il est possible de calculer les dimensions du contenu dans l'iframe, mais comment exactement cela doit être fait, dépend un peu du contenu lui-même et de beaucoup de CSS utilisé (le contenu positionné de manière absolue est extrêmement difficile à mesurer). Si vous utilisez un fichier de réinitialisation css, le résultat diffère de la page a où le fichier de réinitialisation n'est pas utilisé et varie également d'un navigateur à l'autre. La définition de la taille de l'iframe et de son parent est triviale. Ainsi, nous aurions besoin de plus d'informations sur la structure de la page, en particulier le nom du ou des fichiers de réinitialisation CSS utilisés (ou le CSS réel, si vous n'utilisez aucun fichier commun).
Teemu
2
Avez-vous le contrôle sur le document chargé dans l'iframe? Je veux dire, si vous ne le faites pas, alors vous ne pouvez rien faire. Tout doit être fait à l'intérieur du document iframe (ou en accédant à ce document), sinon ce n'est pas possible du tout.
Teemu
1
Non, alors ce n'est pas possible, voir developer.mozilla.org/en-US/docs/Web/Security/… . La page principale ne peut pas accéder à un document interdomaine dans l'iframe (et vice-versa) pour des raisons de sécurité, cela ne peut être contourné que si vous avez le contrôle sur le document affiché dans l'iframe.
Teemu
1
... 15 ans d'Internet disant que c'est impossible ne suffit pas? Avons-nous vraiment besoin d'une nouvelle question à ce sujet?
Kaiido

Réponses:

8

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.postMessageavec l' targetOriginensemble 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.documentque 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-anywhereest 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 postMessageet 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 postMessagepour envoyer la taille iFrame au parent encore loadet encore resize. S'il y a une valeur pour le originparamè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.extseront 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 useProxyparam est défini. Il peut y avoir des paramètres supplémentaires qui seront transmis à postMessagelors 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: autole corps de l'iFrame ( il devrait en être overflow-y: hiddenainsi 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.

Christos Lytras
la source
1
Merci! Très bonne réponse.
ferit le
1
Je vous remercie. Malheureusement, il cors-anywhereest en panne pour le moment, vous ne pouvez donc pas voir la page de démonstration . Je ne veux pas ajouter de capture d'écran du site externe pour des raisons de copyright. J'ajouterai un autre proxy CORS s'il reste en panne longtemps.
Christos Lytras
2

Il y a beaucoup de complications avec la détermination de la taille du contenu dans un iframe, principalement en raison du CSS qui vous permet de faire des choses qui peuvent casser la façon dont vous mesurez la taille du contenu.

J'ai écrit une bibliothèque qui s'occupe de toutes ces choses et qui fonctionne également entre domaines, vous pourriez trouver cela utile.

https://github.com/davidjbradshaw/iframe-resizer

David Bradshaw
la source
1
Cool! Je vais vérifier ça!
ferit le
0

Ma solution est sur GitHub et JSFiddle .

Présentation d'une solution pour un iframe réactif sans hypothèse de rapport d'aspect.

L'objectif est de redimensionner l'iframe si nécessaire, c'est-à-dire lorsque la fenêtre est redimensionnée. Ceci est effectué avec JavaScript pour obtenir la nouvelle taille de fenêtre et redimensionner l'iframe en conséquence.

NB: N'oubliez pas d'appeler la fonction resize après le chargement de la page car la fenêtre n'est pas redimensionnée d'elle-même après le chargement de la page.

Le code

index.html

<!DOCTYPE html>
<!-- Onyr for StackOverflow -->

<html>

<head> 
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <title>Responsive Iframe</title>
    <link rel="stylesheet" type="text/css" href="./style.css">
</head>

<body id="page_body">
    <h1>Responsive iframe</h1>

    <div id="video_wrapper">
        <iframe id="iframe" src="https://fr.wikipedia.org/wiki/Main_Page"></iframe>
    </div>

    <p> 
        Presenting a solution for responsive iframe without aspect
        ratio assumption.<br><br>
    </p>

    <script src="./main.js"></script>
</body>

</html>

style.css

html {
    height: 100%;
    max-height: 1000px;
}

body {
    background-color: #44474a;
    color: white;
    margin: 0;
    padding: 0;
}

#videoWrapper {
    position: relative;
    padding-top: 25px;
    padding-bottom: 100px;
    height: 0;
    margin: 10;
}
#iframe {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

main.js

let videoWrapper = document.getElementById("video_wrapper");

let w;
let h;
let bodyWidth;
let bodyHeight;

// get window size and resize the iframe
function resizeIframeWrapper() {
    w = window.innerWidth;
    h = window.innerHeight;

    videoWrapper.style["width"] = `${w}px`;
    videoWrapper.style["height"] = `${h - 200}px`;
}

// call the resize function when windows is resized and after load
window.onload = resizeIframeWrapper;
window.onresize = resizeIframeWrapper;

J'ai passé un peu de temps là-dessus. J'espère que vous l'apprécierez =)

Modifier C'est probablement la meilleure solution générique. Cependant, lorsqu'il est très petit, l'iframe n'a pas la bonne taille. Ceci est spécifique à l'iframe que vous utilisez. Il n'est pas possible de coder une réponse appropriée à ce phénomène à moins d'avoir le code de l'iframe, car votre code n'a aucun moyen de savoir quelle taille est préférée par l'iframe pour être correctement affiché.

Seuls certains hacks comme celui présenté par @Christos Lytras peuvent faire l'affaire, mais cela ne fonctionnera jamais pour chaque iframe. Juste dans une situation particulière.

Onyr
la source
Merci d'avoir essayé! Cependant, cela ne fonctionne pas comme prévu. Lorsque le contenu d'iframe est petit, le conteneur dispose toujours d'un espace supplémentaire. Voir ceci: jsfiddle.net/6p2suv07/3 J'ai changé le src de l'iframe.
ferit le
Je ne suis pas sûr de comprendre votre problème avec "l'espace supplémentaire". La taille minimale de la fenêtre est de 100 px de largeur. L'iframe s'adapte au conteneur. La disposition à l'intérieur de votre iframe n'est pas sous mon contrôle. J'ai répondu à ta question. Veuillez modifier le vôtre si vous voulez plus d'aide
Onyr
Mettez une photo et décrivez-la
Onyr
iframe prend tout l'espace que le conteneur lui donne, le fait est que lorsque le contenu iframe est petit, il ne nécessite pas tout l'espace qu'il peut prendre. Ainsi, le conteneur doit donner juste assez d'espace à l'iframe, ni plus ni moins.
ferit le
Pour la hauteur de l'iframe, c'est parce que la hauteur de l'espace est limitée par la fenêtre dans mon exemple. C'est ça? Votre exemple est spécifique à l'iframe que vous utilisez, j'ai donné une réponse générale qui devrait être acceptée. Bien sûr, il est possible de modifier le code pour qu'il corresponde parfaitement à votre iframe, mais cela nécessite de connaître le code derrière l'iframe
Onyr
-1

Faire une chose définir un conteneur Iframe une propriété CSS de largeur et hauteur définie

.iframe-container{
     width:100%;
     min-height:600px;
     max-height:100vh;
}

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Aslam khan
la source
Et si le contenu est inférieur à 600 pixels?
ferit
si la hauteur est de 600 si iframe ne sera pas affecté parce que je l' ai mis en forme objet contient
Aslam Khan
Le conteneur ne peut pas être inférieur à 600 pixels, il s'agit d'une violation de l'exigence décrite dans la question.
ferit Il y a