如何在没有宽高比假设的情况下使iframe响应?


14

如何在不假定宽高比的情况下使iframe响应?例如,内容可以具有任何宽度或高度,在渲染之前是未知的。

注意,您可以使用Javascript。

例:

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

调整大小iframe-container以使其内容几乎不能容纳在里面而没有多余的空间,换句话说,有足够的空间容纳内容,因此无需滚动即可显示内容,但没有多余的空间。容器完美地包裹了iframe。

假设内容的纵横比为16:9,说明了如何使iframe响应。但是在这个问题上,纵横比是可变的。


1
让我通过编辑@TravisJ来阐明
Ferit

2
可以在iframe中计算内容的尺寸,但是应该如何精确地完成操作,取决于内容本身以及大量使用的CSS(绝对定位的内容非常难以衡量)。如果您使用的是CSS重置文件,则结果与未使用重置文件的页面会有所不同,并且在浏览器之间也会有所不同。设置iframe及其父对象的大小很简单。因此,我们需要有关页面结构的更多信息,尤其是使用的CSS重置文件的名称(如果不使用任何常用文件,则为实际CSS的名称)。
Teemu

2
您可以控制iframe中加载的文档吗?我的意思是,如果您不这样做,那么您将无能为力。一切都必须在iframe文档中完成(或通过访问该文档),否则这是根本不可能的。
Teemu

1
不,那就不可能了,请参阅developer.mozilla.org/en-US/docs/Web/Security/…。出于安全原因,主页无法访问iframe中的跨域文档(反之亦然),只有在您可以控制iframe中显示的文档的情况下,才可以绕开此页面。
Teemu

1
... 15年的互联网说不可能,这还不够吗?我们真的需要一个新的问题吗?
Kaiido

Answers:


8

无法使用Javascript与其他来源的iFrame进行交互,以获取其大小;唯一的方法是将其window.postMessagetargetOrigin您的域中的设置一起使用,或与*iFrame源中的wildchar一起使用。您可以代理不同来源站点的内容并使用srcdoc,但这被认为是一种hack,它不适用于SPA和许多其他动态页面。

相同来源的iFrame大小

假设我们有两个相同的原始iFrame,其中一个是矮高和固定宽度:

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

和长高的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>

我们可以load使用事件获取iFrame大小,然后使用以下代码iframe.contentWindow.document将其发送到父窗口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>

我们将为两个iFrame获得适当的宽度和高度;您可以在此处在线检查并查看屏幕截图在这里

不同来源的iFrame大小hack不推荐)

这里描述的方法是一种技巧,如果绝对必要并且没有其他方法可以使用。它不适用于大多数动态生成的页面和SPA。该方法使用代理来获取HTML页面的源代码,以绕过CORS策略(这cors-anywhere是创建简单的CORS代理服务器的简单方法,并且具有在线演示https://cors-anywhere.herokuapp.com),然后向该HTML注入JS代码以使用postMessage并发送HTML 的大小iFrame到父文档。它甚至可以处理iFrame resize与iFrame结合使用width: 100%)事件,并将iFrame大小发回给父对象。

patchIframeHtml

该功能可以修补iFrame的HTML代码,并注入自定义JavaScript将使用postMessage到的iFrame大小发送到母公司的loadresize。如果该origin参数有一个值,则将<base/>使用该原始URL 在HTML 元素之前添加一个HTML 元素,因此,类似的HTML URI /some/resource/file.ext将被iFrame中的原始URL正确提取。

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

如果useProxy设置了参数,则使用代理获取绕过CORS的页面HTML的功能。postMessage发送尺寸数据时,可以将其他参数传递给。

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

消息事件处理程序功能与“相同来源iFrame大小”中的功能完全相同

现在,我们可以通过注入自定义JS代码在iFrame中加载跨源域:

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

而且,即使overflow-y: auto将iFrame主体用于iFrame ,我们也可以将iFrame调整到其内容全高的大小,而无需进行任何垂直滚动(应该这样,overflow-y: hidden因此在resize上不会出现滚动条闪烁)。

您可以在此处在线检查。

再次注意这是骇客应该避免 ; 我们无法访问跨域 iFrame文档,也无法注入任何东西。


1
谢谢!好答案。
发酵

1
谢谢。不幸的cors-anywhere是此刻失败了,所以您看不到演示页面。出于版权原因,我不想添加外部网站的屏幕截图。如果长时间不使用,我将添加另一个CORS代理。
Christos Lytras


0

我的解决方案在GitHubJSFiddle上

提出无需宽高比假设的自适应iframe解决方案。

目标是在必要时调整iframe的大小,换句话说,在调整窗口大小时。使用JavaScript执行此操作,以获取新的窗口大小并调整iframe的大小。

注意:加载页面后,请不要忘记调用resize函数,因为加载页面后窗口不会自行调整大小。

编码

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;

我花了一些时间。希望您会喜欢=)

编辑 这可能是最好的通用解决方案。但是,如果尺寸很小,则不会为iframe提供适当的尺寸。这特定于您使用的iframe。除非您拥有iframe的代码,否则不可能为这种现象编写一个正确的答案,因为您的代码无法知道iframe希望正确显示哪种尺寸。

只有像@Christos Lytras提出的那样的一些骇客才能成功,但它永远不会在每个iframe上起作用。只是在特定情况下。


感谢您的尝试!但是,它没有按预期工作。当iframe的内容较小时,该容器仍具有额外的空间。参见:jsfiddle.net/6p2suv07/3我更改了iframe的src。
费里特

我不确定您是否对“多余空间”有疑问。窗口的最小尺寸为100像素宽度。iframe适合容器。iframe内部的布局不在我的控制范围内。我回答了你的问题。如果您需要更多帮助,请编辑您的内容
Onyr

放一张照片并描述它
Onyr

iframe占用了容器给它的所有空间,问题是,当iframe内容很小时,它并不需要它可以占用的所有空间。因此,容器应为iframe留出足够的空间,不少于没有。
费里特

对于iframe的高度,这是因为在我的示例中,空间的高度受窗口的限制。就是这个吗 您的示例特定于您使用的iframe,我给出了一个应接受的一般答案。当然,可以对代码进行调整,使其与您的iframe很好地匹配,但这需要了解iframe背后的代码
Onyr

0

做一件事设置Iframe容器一些宽度和高度设置CSS属性

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

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.