¿Cómo hacer que un iframe responda sin suposición de relación de aspecto?

14

¿Cómo hacer que un iframe responda, sin asumir una relación de aspecto? Por ejemplo, el contenido puede tener cualquier ancho o alto, lo cual es desconocido antes de la representación.

Tenga en cuenta que puede usar Javascript.

Ejemplo:

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

Dimensione esto iframe-containerpara que el contenido apenas encaje dentro sin espacio adicional, en otras palabras, hay suficiente espacio para el contenido para que pueda mostrarse sin desplazamiento, pero sin espacio en exceso. El contenedor envuelve el iframe perfectamente.

Esto muestra cómo hacer que un iframe responda, suponiendo que la relación de aspecto del contenido es 16: 9. Pero en esta pregunta, la relación de aspecto es variable.

fermentar
fuente
1
Permítanme aclarar con una edición @TravisJ
ferit
2
Es posible calcular las dimensiones del contenido en el iframe, pero la forma exacta en que se debe hacer depende un poco del contenido en sí y de una gran parte del CSS utilizado (el contenido posicionado absolutamente es extremadamente difícil de medir). Si está utilizando un archivo de restablecimiento CSS, el resultado difiere de la página en la que no se utiliza el archivo de restablecimiento, y también varía entre navegadores. Establecer el tamaño del iframe y su padre es trivial. Por lo tanto, necesitaríamos más información sobre la estructura de la página, específicamente los nombres de los archivos de restablecimiento de CSS utilizados (o el CSS real, si no está utilizando ningún archivo común).
Teemu
2
¿Tiene control sobre el documento cargado en el iframe? Quiero decir, si no lo haces, entonces no hay nada que puedas hacer. Todo tiene que hacerse dentro del documento del iframe (o accediendo a ese documento), de lo contrario, esto no es posible en absoluto.
Teemu
1
No, entonces no es posible, vea developer.mozilla.org/en-US/docs/Web/Security/… . La página principal no puede acceder a un documento de dominio cruzado en el iframe (y viceversa) por razones de seguridad, esto se puede eludir solo si tiene control sobre el documento que se muestra en el iframe.
Teemu
1
... 15 años de internet diciendo que es imposible no es suficiente? ¿Realmente necesitamos una nueva pregunta sobre esto?
Kaiido

Respuestas:

8

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.postMessagedel targetOriginconjunto 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 loadevento usando iframe.contentWindow.documentque 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-anywherees 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 postMessagey 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á postMessagepara enviar el tamaño de iFrame al padre una loady otra vez resize. Si hay un valor para el originpará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.extserá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 useProxyse establece param. Puede haber parámetros adicionales que se pasarán postMessageal 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: autopara el cuerpo del iFrame ( debe ser overflow-y: hiddenasí 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.

Christos Lytras
fuente
1
¡Gracias! Gran respuesta.
fermentar el
1
Gracias. Lamentablemente no funciona cors-anywhereen este momento, por lo que no puede ver la página de demostración . No quiero agregar una captura de pantalla del sitio externo por razones de derechos de autor. Agregaré otro proxy CORS si eso permanece inactivo por mucho tiempo.
Christos Lytras
2

Hay muchas complicaciones al calcular el tamaño del contenido en un iframe, principalmente debido a que CSS le permite hacer cosas que pueden romper la forma en que mide el tamaño del contenido.

Escribí una biblioteca que se encarga de todas estas cosas y también funciona en varios dominios, puede que le resulte útil.

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

David Bradshaw
fuente
1
¡Frio! ¡Voy a comprobar eso!
Ferit
0

Mi solución está en GitHub y JSFiddle .

Presentar una solución para iframe receptivo sin suposición de relación de aspecto.

El objetivo es cambiar el tamaño del iframe cuando sea necesario, en otras palabras, cuando se cambia el tamaño de la ventana. Esto se realiza con JavaScript para obtener el nuevo tamaño de ventana y cambiar el tamaño del iframe en consecuencia.

Nota: no olvide llamar a la función de cambio de tamaño después de cargar la página, ya que la ventana no cambia de tamaño por sí sola después de cargar la página.

El código

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;

Pasé un tiempo en esto. Espero que lo disfrutes =)

Editar Esta es probablemente la mejor solución genérica. Sin embargo, cuando se hace muy pequeño, el iframe no tiene el tamaño adecuado. Esto es específico del iframe que está utilizando. No es posible codificar una respuesta adecuada para este fenómeno a menos que tenga el código del iframe, ya que su código no tiene forma de saber qué tamaño prefiere el iframe para que se muestre correctamente.

Solo algunos trucos como el presentado por @Christos Lytras pueden hacer el truco, pero nunca funcionará para cada iframe. Solo en una situación particular.

Onyr
fuente
¡Gracias por intentarlo! Sin embargo, no funciona como se esperaba. Cuando el contenido del iframe es pequeño, el contenedor todavía tiene espacio extra. Vea esto: jsfiddle.net/6p2suv07/3 Cambié el src del iframe.
Ferit
No estoy seguro de entender su problema con el "espacio extra". El tamaño mínimo de la ventana es de 100 px de ancho. El iframe se ajusta al contenedor. El diseño dentro de su iframe no está bajo mi control. Respondí tu pregunta.
Edita el
Pon una foto y
descríbela
el iframe ocupa todo el espacio que le da el contenedor, la cuestión es que, cuando el contenido del iframe es pequeño, no requiere todo el espacio que puede ocupar. Por lo tanto, el contenedor debe dar suficiente espacio al iframe, nada menos y nada más.
Ferit
Para la altura del iframe, esto se debe a que la altura del espacio está limitada por la ventana en mi ejemplo. ¿Eso es esto? Su ejemplo es específico para el iframe que usa, le di una respuesta general que debería ser aceptada. Por supuesto, es posible ajustar el código para que se ajuste bien con su iframe, pero esto requiere conocer el código detrás del iframe
Onyr
-1

Haga una cosa: establezca el contenedor de marco flotante, un poco de ancho y alto, establezca la propiedad CSS

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Aslam Khan
fuente
¿Qué pasa si el contenido es más pequeño que 600px?
ferita
si la altura es 600, por lo que el iframe no se verá afectado porque lo configuré en el objeto que contiene
Aslam khan hace
El contenedor no puede ser más pequeño que 600px, esto es una violación del requisito descrito en la pregunta.
ferit hace