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

fer*_*rit 21 html javascript css iframe responsive-design

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

请注意,您可以使用 Javascript。

例子:

<div id="iframe-container">
    <iframe/>
</div>
Run Code Online (Sandbox Code Playgroud)

调整它的大小iframe-container,使其内容几乎不适合内部而没有额外空间,换句话说,有足够的空间来容纳内容,因此可以在不滚动的情况下显示,但没有多余的空间。容器完美地包裹了 iframe。

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

Chr*_*ras 8

无法使用 Javascript 与不同来源的 iFrame 进行交互以获取其大小;做到这一点的唯一方法是通过使用window.postMessagetargetOrigin设置您的域名或wildchar*与iframe源。您可以代理不同源站点的内容并使用srcdoc,但这被认为是一种黑客行为,它不适用于 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>
Run Code Online (Sandbox Code Playgroud)

和一个长高的 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>
Run Code Online (Sandbox Code Playgroud)

我们可以load使用iframe.contentWindow.document我们将使用postMessage以下方法发送到父窗口的事件获取 iFrame 大小:

<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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

patchIframeHtml

该功能可以修补iFrame的HTML代码,并注入自定义JavaScript将使用postMessage到的iFrame大小发送到母公司的loadresize。如果该origin参数有值,则 HTML<base/>元素将使用该原始 URL 附加到头部,因此,/some/resource/file.extiFrame 内的原始 URL 将正确获取HTML URI 等。

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;
}
Run Code Online (Sandbox Code Playgroud)

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();
  });
}
Run Code Online (Sandbox Code Playgroud)

消息事件处理函数与“Same origin iFrame size”中的完全相同。

我们现在可以通过注入自定义 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>
Run Code Online (Sandbox Code Playgroud)

我们会将 iFrame 调整为它的内容全高,即使overflow-y: auto用于 iFrame 主体也不会进行任何垂直滚动(它应该是overflow-y: hidden这样我们不会在调整大小时滚动条闪烁)。

您可以在此处在线查看

再次注意,这是一种黑客行为应该避免;我们无法访问跨源iFrame 文档,也无法注入任何类型的东西。