Come rendere reattivo un iframe senza supporre le proporzioni?


14

Come rendere reattivo un iframe, senza assumere un formato? Ad esempio, il contenuto può avere qualsiasi larghezza o altezza, che è sconosciuto prima del rendering.

Nota, puoi usare Javascript.

Esempio:

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

Dimensionalo in iframe-containermodo tale che il suo contenuto si adatti a malapena all'interno senza spazio extra, in altre parole, c'è abbastanza spazio per il contenuto in modo che possa essere mostrato senza scorrere, ma senza spazio in eccesso. Il contenitore avvolge perfettamente l'iframe.

Questo mostra come rendere reattivo un iframe, supponendo che le proporzioni del contenuto siano 16: 9. Ma in questa domanda, le proporzioni sono variabili.


1
Vorrei chiarire con una modifica @TravisJ
Ferit

2
È possibile calcolare le dimensioni del contenuto nell'iframe, ma come esattamente dovrebbe essere fatto, dipende un po 'dal contenuto stesso e molti CSS utilizzati (il contenuto assolutamente posizionato è estremamente difficile da misurare). Se si utilizza un file di ripristino CSS, il risultato differisce da una pagina in cui non viene utilizzato il file di ripristino e varia anche tra i browser. Impostare le dimensioni dell'iframe e del suo genitore è banale. Pertanto, avremmo bisogno di ulteriori informazioni sulla struttura della pagina, in particolare il nome (i) dei file di ripristino CSS utilizzati (o del CSS reale, se non si utilizzano file comuni).
Teemu,

2
Hai il controllo del documento caricato nell'iframe? Voglio dire, se non lo fai, allora non c'è niente che tu possa fare. Tutto deve essere fatto all'interno del documento iframe (o accedendo a quel documento), altrimenti non è affatto possibile.
Teemu,

1
No, quindi non è possibile, consultare developer.mozilla.org/en-US/docs/Web/Security/… . La pagina principale non può accedere a un documento tra domini nell'iframe (e viceversa) per motivi di sicurezza, questo può essere aggirato solo se si ha il controllo sul documento mostrato nell'iframe.
Teemu,

1
... 15 anni di internet che dicono che è impossibile non è abbastanza? Abbiamo davvero bisogno di una nuova domanda al riguardo?
Kaiido,

Risposte:


8

Non è possibile interagire con un iFrame di origine diversa utilizzando Javascript in modo da ottenere le dimensioni; l'unico modo per farlo è usare window.postMessagecon il targetOriginset per il tuo dominio o il jolly *dalla fonte iFrame. È possibile eseguire il proxy dei contenuti dei diversi siti di origine e utilizzarli srcdoc, ma questo è considerato a hack e non funzionerà con SPA e molte altre pagine più dinamiche.

Stessa dimensione iFrame di origine

Supponiamo di avere due identici iFrame di origine, uno di altezza ridotta e larghezza fissa:

<!-- 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>

e una lunga iFrame:

<!-- 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>

Siamo in grado di ottenere le dimensioni di iFrame loadsull'evento utilizzando iframe.contentWindow.documentquello che invieremo alla finestra principale utilizzando 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>

Otterremo larghezza e altezza adeguate per entrambi iFrame; puoi controllarlo online qui e vedere lo screenshot qui .

Hack di dimensioni iFrame di origine diversa ( non consigliato )

Il metodo qui descritto è un hack e dovrebbe essere usato se è assolutamente necessario e non c'è altro modo per aggirare; non funzionerà per la maggior parte delle pagine dinamiche generate e SPA. Il metodo recupera il codice sorgente HTML della pagina utilizzando un proxy per bypassare la politica CORS ( cors-anywhereè un modo semplice per creare un semplice server proxy CORS e ha una demo onlinehttps://cors-anywhere.herokuapp.com ) quindi inietta il codice JS in tale HTML per utilizzare postMessagee inviare la dimensione del iFrame al documento principale. Gestisce persino iFrame resize( combinato con iFramewidth: 100% evento ) e riporta le dimensioni di iFrame al genitore.

patchIframeHtml:

Una funzione per correggere il codice HTML di iFrame e iniettare Javascript personalizzato che verrà utilizzato postMessageper inviare la dimensione di iFrame al genitore loade accenderlo resize. Se esiste un valore per il originparametro, un <base/>elemento HTML verrà anteposto all'head utilizzando quell'URL di origine, pertanto, gli URI HTML come /some/resource/file.extverranno recuperati correttamente dall'URL di origine all'interno dell'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 funzione per ottenere una pagina HTML che ignora il CORS usando un proxy se useProxyparam è impostato. Ci possono essere parametri aggiuntivi che verranno passati a postMessagequando si inviano i dati sulle dimensioni.

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 funzione di gestione degli eventi dei messaggi è esattamente la stessa di "Stessa dimensione iFrame di origine" .

Ora possiamo caricare un dominio di origine incrociata all'interno di un iFrame con il nostro codice JS personalizzato inserito:

<!-- 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>

E ridimensioneremo l'iFrame in base al suo contenuto a tutta altezza senza alcuno scorrimento verticale anche usando overflow-y: autoper il corpo dell'iFrame ( dovrebbe essere overflow-y: hiddencosì non si ottiene uno sfarfallio della barra di scorrimento al ridimensionamento ).

Puoi verificarlo online qui .

Ancora una volta notare che questo è un trucco e dovrebbe essere evitato ; non possiamo accedere al documento iFrame di Cross-Origin né iniettare alcun tipo di cose.


1
Grazie! Bella risposta.
ferit

1
Grazie. Purtroppo cors-anywhereal momento non è attivo, quindi non puoi vedere la pagina demo . Non voglio aggiungere uno screenshot del sito esterno per motivi di copyright. Aggiungerò un altro proxy CORS se questo rimane a lungo inattivo.
Christos Lytras,

2

Esistono molte complicazioni nell'elaborare la dimensione del contenuto in un iframe, principalmente a causa del CSS che ti consente di fare cose che possono spezzare il modo in cui misuri la dimensione del contenuto.

Ho scritto una biblioteca che si occupa di tutte queste cose e funziona anche su più domini, potresti trovarla utile.

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


1
Freddo! Lo controllerò!
ferit

0

La mia soluzione è su GitHub e JSFiddle .

Presentazione di una soluzione per iframe reattivo senza ipotesi sulle proporzioni.

L'obiettivo è ridimensionare l'iframe quando necessario, in altre parole quando la finestra viene ridimensionata. Questo viene eseguito con JavaScript per ottenere la nuova dimensione della finestra e ridimensionare di conseguenza l'iframe.

NB: Non dimenticare di chiamare la funzione di ridimensionamento dopo il caricamento della pagina poiché la finestra non viene ridimensionata da sola dopo il caricamento della pagina.

Il codice

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;

Ho trascorso del tempo su questo. Spero ti piacerà =)

Modifica Questa è probabilmente la migliore soluzione generica. Tuttavia, se reso molto piccolo, all'iframe non viene data la dimensione corretta. Questo è specifico dell'iframe che stai utilizzando. Non è possibile codificare una risposta corretta per questo fenomeno se non si dispone del codice dell'iframe, poiché il codice non ha modo di sapere quale dimensione è preferita dall'iframe per essere visualizzata correttamente.

Solo alcuni hack come quello presentato da @Christos Lytras possono fare il trucco, ma non funzionerà mai per ogni iframe. Proprio in una situazione particolare.


Grazie per averci provato! Tuttavia, non funziona come previsto. Quando il contenuto di iframe è piccolo, il contenitore ha ancora spazio extra. Vedi questo: jsfiddle.net/6p2suv07/3 Ho cambiato l'src dell'iframe.
ferit

Non sono sicuro di capire il tuo problema con "spazio extra". La dimensione minima della finestra è di 100 px di larghezza. L'iframe si adatta al contenitore. Il layout all'interno del tuo iframe non è sotto il mio controllo. Ho risposto alla tua domanda. Modifica il tuo se desideri ulteriore aiuto
Onyr,

Metti una foto e descrivila
Onyr il

iframe occupa tutto lo spazio che il contenitore gli dà, il fatto è che quando il contenuto di iframe è piccolo, non richiede tutto lo spazio che può occupare. Quindi, il contenitore dovrebbe dare lo spazio sufficiente all'iframe, non meno niente di più.
ferit

Per l'altezza dell'iframe, questo è perché l'altezza dello spazio è limitata dalla finestra nel mio esempio. Questo è questo? Il tuo esempio è specifico dell'iframe che usi, ho dato una risposta generale che dovrebbe essere accettata. Certo, è possibile modificare il codice in modo che si adatti perfettamente al tuo iframe ma questo richiede di conoscere il codice dietro l'iframe
Onyr

-1

Fai una cosa per impostare il contenitore Iframe con la larghezza e l'altezza impostando la proprietà CSS

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}

Cosa succede se il contenuto è inferiore a 600 px?
ferit

se l'altezza è 600, quindi iframe non sarà influenzato perché l'ho impostato per contenere l'oggetto
Aslam khan

Il contenitore non può essere inferiore a 600 px, si tratta di una violazione del requisito descritto nella domanda.
ferit
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.