在网页中根据 iframe 的内容自动调节其高度

2024-09-13#Javascript#iframe

<iframe>(Inline Frame 的缩写)是 HTML 中的内联框架元素。它可以在一个 HTML 文档中嵌入另一个独立的 HTML 文档。因此常常用于在网页中嵌入外部内容。但是,由于在渲染父网页时,浏览器并不知道嵌入其中的iframe的高度,因此默认情况下,无法根据 iframe 的内容自动调节其高度。实际中,可以通过多种方法,利用 Javascript实现。本文介绍使用 postMessage 来实现。

postMessage 简介 🔗

postMessage 是 HTML5 引入的一种跨源(cross-origin)通信机制。它允许在不同源的窗口(window 对象)、文档(document 对象)、框架(iframe)之间安全地传递消息。

基本用法 🔗

  • 发送消息:otherWindow.postMessage(message, targetOrigin)
  • 接收消息:在接收消息的窗口中,需要添加一个 message 事件监听器来处理接收到的消息。

自动调节 iframe 的高度 🔗

在子页面发送网页高度 🔗

在子 HTML 页面(即 iframe 指向的页面)中,重置其样式,然后使用 postMessage 发送高度给父 HTML 页面。简化的示例如下:

<html lang="zh-CN" dir="ltr">
  <head>
    <style type="text/css">
      html,
      body {
        overflow: hidden;
      }
      body {
        margin: 0;
        padding: 0;
      }
    </style>

    <script type="text/javascript">
      document.addEventListener("DOMContentLoaded", function () {
        const e = document.getElementById("main");
        parent.postMessage({ height: e.clientHeight }, "*");
      });
    </script>
  </head>
  <body>
    <div id="main">
      <!-- 在此添加网页内容 -->
    </div>
  </body>
</html>

在父页面监听高度变化 🔗

在父页面中监听 message 事件,调整 iframe 的高度。简化的示例如下:

<script type="text/javascript">
  const iframeSrc = "http://localhost:8080/html"; // 子页面的地址

  const element = document.getElementById("iframe-container");
  var iframe = document.createElement("iframe");
  iframe.setAttribute("src", iframeSrc);
  iframe.setAttribute("frameborder", "0");
  iframe.setAttribute("width", "100%");
  iframe.setAttribute("height", "0");

  element.replaceChildren(iframe); // 将 `#iframe-container` 的内容替换为 iframe。
  window.addEventListener(
    "message",
    function (event) {
      const eventData = event.data;

      // 出于安全考虑,在此处检查事件的 origin
      if (event.origin === "http://localhost:8080") {
        const height = eventData.height;
        iframe.height = height + "px";
      }
    },
    false,
  );
</script>
<!-- iframe 容器 -->
<div id="iframe-container">
  <!-- 在加载 iframe 之前的内容(可选) -->
  <div>加载中...</div>
</div>

以上使用的是 replaceChildren 修改 iframe 容器里的内容。也可以按需使用 replaceWithappendChild 等方法修改页面。但是需要注意的是,在一些老的浏览器中,可能不支持 replaceChildren 方法,此时会遇到类似 replaceChildrenis is not a function 的错误。如果非要使用 replaceChildren ,那么可以使用如下的迂回方法:

<script type="text/javascript">
  const iframeSrc = "http://localhost:8080/html"; // 子页面的地址

  const element = document.getElementById("iframe-container");
  const iframe = document.createElement("iframe");
  iframe.id = "iframe-child";
  iframe.setAttribute("src", iframeSrc);
  iframe.setAttribute("frameborder", "0");
  iframe.setAttribute("width", "100%");
  iframe.setAttribute("height", "0");

  if (event.replaceChildren) {
    element.replaceChildren(iframe); // 将 `#iframe-container` 的内容替换为 iframe。
  } else {
    // 通过修改元素的 innerHTML 来修改其内容
    element.innerHTML = "";
    element.innerHTML = iframe.outerHTML;
  }

  const child = document.getElementById(iframe.id);

  window.addEventListener(
    "message",
    function (event) {
      const eventData = event.data;

      // 出于安全考虑,在此处检查事件的 origin
      if (event.origin === "http://localhost:8080") {
        const height = eventData.height;
        child.height = height + "px";
      }
    },
    false,
  );
</script>
<!-- iframe 容器 -->
<div id="iframe-container">
  <!-- 在加载 iframe 之前的内容(可选) -->
  <div>加载中...</div>
</div>

处理子页面中的图片 🔗

以上介绍的方法,只能适用于固定高度页面的内容。由于图片是异步加载的,如果子页面中包含图片资源的话,在 DOMContentLoaded 事件后直接获取页面元素的高度时,此时图片可能还没有加载,取得的高度就是错误的。解决方法是在图片加载后,再次更新高度。为此,可使用如下的代码,在子页面中发送页面高度给父页面:

<script type="text/javascript">
(function () {
  function notify() {
    const e = document.getElementById("main");
    parent.postMessage({ height: e.clientHeight }, "*");
  }
  document.addEventListener("DOMContentLoaded", function () {
    notify();

    const images = document.querySelectorAll("img");
    for (var i = 0; i < images.length; i++) {
      const img = images[i];
      img.addEventListener("load", notify);
      if (img.complete) {
        notify();
      }
    }
  });
})();
</script>

测试 🔗

代码开发完成后,务必在不同的浏览器(包括手机端浏览器)进行测试,确保这种方法能够兼容足够的使用场景,才算开发完成。

其他方法 🔗

处理自适应高度的 iframe 并没有唯一的方法,也没有一锤定音的方法。如果在某些设备或者浏览器上的效果不理想,那么就要进行调试,或者使用其他的方法或者现成工具。在 Stack Overflow 上还可以找到其他方法:


加载中...