Como tornar um iframe responsivo sem assumir a taxa de proporção?

14

Como tornar um iframe responsivo, sem assumir uma proporção? Por exemplo, o conteúdo pode ter qualquer largura ou altura desconhecida antes da renderização.

Observe que você pode usar Javascript.

Exemplo:

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

Dimensione isso iframe-containerpara que o conteúdo mal caiba dentro sem espaço extra; em outras palavras, haverá espaço suficiente para o conteúdo, para que possa ser exibido sem rolagem, mas sem excesso de espaço. O container envolve o iframe perfeitamente.

Isso mostra como tornar um iframe responsivo, assumindo que a proporção do conteúdo seja 16: 9. Mas nesta questão, a proporção é variável.

ferit
fonte
11
Deixe-me esclarecer com uma edição @TravisJ
ferit
2
É possível calcular as dimensões do conteúdo no iframe, mas como exatamente isso deve ser feito depende um pouco do conteúdo em si e de grande parte do CSS usado (o conteúdo absolutamente posicionado é extremamente difícil de medir). Se você estiver usando um arquivo de redefinição de css, o resultado será diferente da página em que o arquivo de redefinição não é usado e também varia entre os navegadores. Definir o tamanho do iframe e seu pai é trivial. Portanto, precisaríamos de mais informações sobre a estrutura da página, especificamente os nomes dos arquivos de redefinição de CSS usados ​​(ou o CSS real, se você não estiver usando nenhum arquivo comum).
Teemu
2
Você tem controle sobre o documento carregado no iframe? Quero dizer, se não, então não há nada que você possa fazer. Tudo precisa ser feito dentro do documento iframe (ou acessando esse documento); caso contrário, isso não é possível.
Teemu
11
Não, não é possível, consulte developer.mozilla.org/en-US/docs/Web/Security/… . A página principal não pode acessar um documento de domínio cruzado no iframe (e vice-versa) por motivos de segurança; isso pode ser contornado apenas se você tiver controle sobre o documento mostrado no iframe.
Teemu
11
... 15 anos de internet dizendo que é impossível não é suficiente? Nós realmente precisamos de uma nova pergunta sobre isso?
Kaiido 27/04

Respostas:

8

Não é possível interagir com um iFrame de origem diferente usando Javascript, para obter o tamanho dele; a única maneira de fazer isso é usando window.postMessageo targetOriginconjunto no seu domínio ou o caractere curinga *da fonte do iFrame. Você pode proxy o conteúdo dos diferentes sites de origem e uso srcdoc, mas isso é considerado um hack e não funciona com SPAs e muitas outras páginas mais dinâmicas.

Mesmo tamanho de iFrame de origem

Suponha que tenhamos dois iFrames de origem iguais, um de altura curta e largura fixa:

<!-- 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 um iFrame de altura alta:

<!-- 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 obter o tamanho do iFrame no loadevento usando o iframe.contentWindow.documentque enviaremos para a janela pai 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>

Obteremos largura e altura adequadas para os dois iFrames; você pode conferir on-line aqui e ver a captura de tela aqui .

Tamanho diferente origem iFrame corte ( não recomendado )

O método descrito aqui é um hack e deve ser usado se for absolutamente necessário e não houver outro caminho; não funcionará para as páginas e SPAs gerados mais dinâmicos. O método busca o código-fonte HTML da página usando um proxy para ignorar a política do CORS ( cors-anywhereé uma maneira fácil de criar um servidor proxy CORS simples e possui uma demonstração onlinehttps://cors-anywhere.herokuapp.com ) e injeta o código JS nesse HTML para usar postMessagee enviar o tamanho do iFrame para o documento pai. Ele até lida com o evento iFrame resize( combinado com o iFramewidth: 100% ) e publica o tamanho do iFrame de volta ao pai.

patchIframeHtml:

Uma função para corrigir o código HTML do iFrame e injetar Javascript personalizado que será usado postMessagepara enviar o tamanho do iFrame para o pai loade assim por diante resize. Se houver um valor para o originparâmetro, um <base/>elemento HTML será anexado ao cabeçalho usando o URL de origem; portanto, os URIs HTML /some/resource/file.extserão buscados corretamente pelo URL de origem dentro do 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:

Uma função para obter um HTML da página ignorando o CORS usando um proxy se useProxyparam estiver definido. Pode haver parâmetros adicionais que serão passados ​​para o postMessagequando enviar dados de tamanho.

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();
  });
}

A função de manipulador de eventos de mensagem é exatamente a mesma que em "Mesmo tamanho de iFrame de origem" .

Agora podemos carregar um domínio de origem cruzada dentro de um iFrame com nosso código JS personalizado injetado:

<!-- 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 nós vamos ajustar o tamanho do iFrame ao seu conteúdo em altura total, sem nenhuma rolagem vertical, mesmo usando overflow-y: autoo corpo do iFrame ( deve ser overflow-y: hiddenassim, para que a barra de rolagem não pisque no redimensionamento ).

Você pode conferir online aqui .

Novamente, observe que isso é um hack e deve ser evitado ; não podemos acessar o documento iFrame de origem cruzada nem injetar qualquer tipo de coisa.

Christos Lytras
fonte
11
Obrigado! Ótima resposta.
ferit 28/04
11
Obrigado. Infelizmente, cors-anywhereestá desativado no momento, portanto você não pode ver a página de demonstração . Não quero adicionar uma captura de tela do site externo por motivos de direitos autorais. Vou adicionar um outro proxy CORS se isso permanecer inativo por muito tempo.
Christos Lytras
2

Existem muitas complicações com a elaboração do tamanho do conteúdo em um iframe, principalmente devido ao CSS, permitindo que você faça coisas que podem quebrar a maneira como você mede o tamanho do conteúdo.

Eu escrevi uma biblioteca que cuida de todas essas coisas e também funciona em vários domínios, você pode achar útil.

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

David Bradshaw
fonte
11
Legal! Vou verificar isso!
ferit 30/04
0

Minha solução está no GitHub e JSFiddle .

Apresentando uma solução para iframe responsivo sem suposição de proporção.

O objetivo é redimensionar o iframe quando necessário, ou seja, quando a janela é redimensionada. Isso é realizado com JavaScript para obter o novo tamanho da janela e redimensionar o iframe em consequência.

Nota: Não se esqueça de chamar a função de redimensionamento após o carregamento da página, pois a janela não é redimensionada por si só após o carregamento da página.

O 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;

Passei algum tempo nisso. Espero que você goste =)

Editar Esta é provavelmente a melhor solução genérica. No entanto, quando muito pequeno, o iframe não recebe o tamanho adequado. Isso é específico para o iframe que você está usando. Não é possível codificar uma resposta adequada para esse fenômeno, a menos que você possua o código do iframe, pois seu código não tem como saber qual tamanho é preferido pelo iframe para ser exibido corretamente.

Apenas alguns hacks como o apresentado por @Christos Lytras podem fazer o truque, mas nunca funcionará para todos os iframes. Apenas em uma situação particular.

Onyr
fonte
Obrigado por tentar! No entanto, não está funcionando como esperado. Quando o conteúdo do iframe é pequeno, o contêiner ainda possui espaço extra. Veja isto: jsfiddle.net/6p2suv07/3 Alterei o src do iframe.
ferit 27/04
Não tenho certeza de entender seu problema com "espaço extra". O tamanho mínimo da janela é 100 px de largura. O iframe se encaixa no contêiner. O layout dentro do seu iframe não está sob meu controle. Eu respondi sua pergunta. Por favor, edite o seu se você quiser mais ajuda
Onyr 27/04
Coloque uma foto e descreva-a
Onyr 27/04
O iframe ocupa todo o espaço que o contêiner lhe concede, mas quando o conteúdo do iframe é pequeno, ele não exige todo o espaço que pode ocupar. Portanto, o contêiner deve dar espaço suficiente ao iframe, nem menos nem mais.
ferit 27/04
Para a altura do iframe, isso ocorre porque a altura do espaço é limitada pela janela no meu exemplo. É isso? Seu exemplo é específico para o iframe que você usa. Dei uma resposta geral que deve ser aceita. Claro, é possível ajustar o código para que ele se encaixe perfeitamente no seu iframe, mas isso exige que você saiba o código por trás do iframe
Onyr
-1

Faça uma coisa definir o contêiner Iframe com alguma propriedade CSS de largura e altura

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
Aslam khan
fonte
E se o conteúdo for menor que 600 px?
ferit
Se a altura é de 600 modo iframe não afetou porque eu defini-lo objeto ajuste conter
Aslam Khan
O contêiner não pode ser menor que 600 px, isso é uma violação do requisito descrito na pergunta.
ferit