【问题标题】:How to make an iframe responsive without aspect ratio assumption?如何在没有纵横比假设的情况下使 iframe 响应?
【发布时间】:2020-08-07 23:17:20
【问题描述】:

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

注意,您可以使用 Javascript。

例子:

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

调整iframe-container 的大小,使其内容在没有额外空间的情况下勉强装在里面,换句话说,有足够的空间容纳内容,因此无需滚动即可显示,但没有多余的空间。 Container 完美地包裹了 iframe。

This 展示了如何使 iframe 响应式,假设内容的纵横比为 16:9。但在这个问题中,纵横比是可变的。

【问题讨论】:

  • 让我通过编辑@TravisJ 来澄清一下
  • iframe中内容的尺寸是可以计算出来的,但是具体应该怎么做,有点取决于内容本身,还有很多用到的CSS(绝对定位的内容非常难以测量)。如果您使用的是 css 重置文件,则结果与未使用重置文件的页面不同,并且因浏览器而异。设置 iframe 及其父级的大小很简单。因此,我们需要有关页面结构的更多信息,特别是使用的 CSS 重置文件的名称(或实际的 CSS,如果您不使用任何通用文件)。
  • 您是否可以控制 iframe 中加载的文档?我的意思是,如果您不这样做,那么您将无能为力。一切都必须在 iframe 文档中完成(或通过访问该文档),否则这根本不可能。
  • 不,那是不可能的,见developer.mozilla.org/en-US/docs/Web/Security/…。出于安全原因,主页无法访问 iframe 中的跨域文档(反之亦然),只有当您可以控制 iframe 中显示的文档时,才能避免这种情况。
  • ... 15 年的互联网说这是不可能的还不够吗?我们真的需要一个新的问题吗?

标签: javascript html css iframe responsive-design


【解决方案1】:

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

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

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

【讨论】:

  • 如果内容小于 600px 怎么办?
  • 如果高度为 600 则 iframe 不会受到影响,因为我将其设置为适合包含的对象
  • 容器不能小于600px,这违反了问题中描述的要求。
【解决方案2】:

计算 iframe 中内容的大小会带来很多复杂性,这主要是因为 CSS 使您能够做的事情可能会破坏您测量内容大小的方式。

我编写了一个库,可以处理所有这些事情并且可以跨域工作,您可能会发现它很有帮助。

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

【讨论】:

  • 酷!去检查一下!
【解决方案3】:

无法使用 Javascript 与不同来源的 iFrame 进行交互以获取它的大小;唯一的方法是使用window.postMessage 并将targetOrigin 设置为您的域或来自iFrame 源的通配符*。您可以代理不同源站点的内容并使用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>

我们可以使用iframe.contentWindow.document 获取load 事件的 iFrame 大小,我们将使用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 获得正确的宽度和高度;您可以在线查看here 并查看截图here。

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

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

patchIframeHtml:

修补 iFrame HTML 代码并注入自定义 Javascript 的函数,该 Javascript 将使用 postMessage 将 iFrame 大小发送到 load 和 resize 上的父级。如果 origin 参数有值,则 HTML &lt;base/&gt; 元素将使用该原始 URL 附加到头部,因此,像 /some/resource/file.ext 这样的 HTML URI 将被 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 参数,则使用代理获取页面 HTML 绕过 CORS 的函数。发送尺寸数据时,可以有额外的参数传递给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();
  });
}

消息事件处理函数与"Same origin iFrame size"中完全一样。

我们现在可以在 iFrame 中加载一个跨域域,并注入我们的自定义 JS 代码:

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

我们将让 iFrame 调整到其内容的全高,即使使用 overflow-y: auto 作为 iFrame 主体(它应该是 overflow-y: hidden,所以我们不会在调整大小时滚动条闪烁)。

您可以在线查看here。

再次注意到这是一种黑客攻击,应该避免;我们不能访问Cross-Origin iFrame 文档,也不能注入任何东西。

【讨论】:

  • 谢谢!很好的答案。
  • 谢谢。不幸的是,cors-anywhere 目前已关闭,因此您看不到 demo 页面。出于版权原因,我不想添加外部网站的屏幕截图。如果它长时间保持关闭,我将添加另一个 CORS 代理。
  • @ferit,这个答案很好,你为这篇文章分配了全部赏金,但为什么你没有通过绿色勾号将其检查为正确答案?
【解决方案4】:

我的解决方案是 GitHub 和 JSFiddle。

在没有纵横比假设的情况下为响应式 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 的代码,否则无法为这种现象编写正确的答案,因为您的代码无法知道 iframe 首选哪种尺寸才能正确显示。

只有像@Christos Lytras 提出的一些技巧可以成功,但它永远不会适用于每个 iframe。只是在特定情况下。

【讨论】:

  • 感谢您的尝试!但是,它没有按预期工作。当 iframe 的内容很小时,容器仍然有额外的空间。看到这个:jsfiddle.net/6p2suv07/3我改变了 iframe 的 src。
  • 我不确定您是否理解“额外空间”的问题。窗口最小尺寸为 100 像素宽度。 iframe 适合容器。 iframe 内的布局不在我的控制范围内。我回答了你的问题。如果您需要更多帮助,请编辑您的
  • 放一张图描述一下
  • iframe 占用了容器给它的所有空间,问题是,当 iframe 内容很小时,它不需要它可以占用的所有空间。因此,容器应该为 iframe 提供足够的空间,不多不少。
  • 对于 iframe 的高度,这是因为在我的示例中,空间的高度受到窗口的限制。就是这个?您的示例特定于您使用的 iframe,我给出了一个应该被接受的一般答案。当然,可以调整代码以使其与您的 iframe 完美匹配,但这需要了解 iframe 背后的代码
猜你喜欢
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
  • 2018-05-30
  • 2014-03-13
  • 2023-03-03
  • 2018-01-18
  • 2018-01-06
  • 2017-07-17
相关资源
最近更新 更多