微前端 之 iframe 完全指南

33 阅读14分钟

iframe 是 HTML 中用于在当前页面内嵌入另一个独立文档的标签,形成“页面中的页面”。其核心价值在于隔离内容渲染环境,但需严格处理跨域安全与性能问题。

嵌入独立文档:在当前页面中加载并显示另一个 HTML 页面、PDF 或第三方资源(如地图、视频),形成独立的浏览上下文(拥有自己的 DOM、JavaScript 执行环境和会话历史)。

内容隔离:嵌入内容与父页面互不干扰(CSS 样式、JavaScript 变量默认不共享),避免第三方代码污染主页面逻辑。

现代浏览器(如 Chromium)通常为每个 iframe 分配独立的渲染进程或 JS 堆,导致内存占用显著增加(3 个 iframe 可使内存消耗提升 30%~50%)

 iframe 基础属性

[Exposed=Window]
interface HTMLIFrameElement : HTMLElement {
  [HTMLConstructor] constructor();

  [CEReactions, ReflectURL] attribute USVString src;
  [CEReactions] attribute (TrustedHTML or DOMString) srcdoc;
  [CEReactions, Reflect] attribute DOMString name;
  [SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList sandbox;
  [CEReactions, Reflect] attribute DOMString allow;
  [CEReactions, Reflect] attribute boolean allowFullscreen;
  [CEReactions, Reflect] attribute DOMString width;
  [CEReactions, Reflect] attribute DOMString height;
  [CEReactions] attribute DOMString referrerPolicy;
  [CEReactions] attribute DOMString loading;
  readonly attribute Document? contentDocument;
  readonly attribute WindowProxy? contentWindow;
  Document? getSVGDocument();

  // also has obsolete members
};

根据 HTML 标准,<iframe> 元素的默认尺寸为:

  • 宽度300 像素
  • 高度150 像素

HTML 属性 height 的值默认单位是像素,不需要写 px

<iframe src="https://juejin.cn/" frameborder="0" width="100%" height="100"></iframe>

image.png

/* 全局 iframe 样式 */
iframe {
  height: 100vh;
}

src 属性

指定要加载的文档 URL。可以是同源或跨域的绝对/相对路径。

<iframe src="https://www.baidu.com" frameborder="0"></iframe>

image.png

image.png 百度服务器在它的响应中设置了一个 Content Security Policy (CSP) 指令,明确禁止了你的网页将 https://www.baidu.com 嵌入到 iframe 中。

image.png

srcdoc

直接书写 HTML 代码作为 iframe 内容,优先级高于 src。适用于展示简单片段,避免额外网络请求。

<iframe frameborder="0" srcdoc="<h2>Hello</h2><p>这是内嵌内容</p>"></iframe>

image.png

name 属性

用于 <a> 或 <form> 的 target 属性,或 JavaScript 中的 window.open 指定目标。

<iframe name="myFrame"></iframe>

<a href="page.html" target="myFrame">在 iframe 中打开</a>

sandbox  属性

sandbox 对 iframe 内容施加一系列限制,为空时启用所有限制。通过添加标志位放宽特定权限。

标志位作用
allow-same-origin允许 iframe 将其来源视为与父页面同源(否则强制为唯一独立源)
allow-scripts允许执行 JavaScript,包括 alertpostMessage 等
allow-forms允许表单提交
allow-popups允许打开弹窗(window.open
allow-top-navigation允许 iframe 将最顶层窗口(top)导航到新 URL
allow-top-navigation-by-user-activation只有在用户交互(点击等)后才允许顶层导航
allow-modals允许模态框(alertconfirmprompt
allow-downloads允许下载文件
allow-presentation允许 iframe 启动演示请求

1、sandbox, 空值 (sandbox 或 sandbox=""):启用全部限制。

<iframe src="page.html" frameborder="0" sandbox></iframe>

页面中的脚本执行被阻止,因为该文档所在的 iframe 启用了沙箱(sandbox),但没有授予 allow-scripts 权限。

image.png

2、设置 allow-scripts

page.html 文件

<body>
<h1>这是一个page页面</h1>
</body>
<script>
console.log("这是一个page页面");
</script>
<iframe src="page.html" frameborder="0" sandbox="allow-scripts"></iframe>
  • 根据 HTML 规范,当一个文档被沙箱化且没有 allow-same-origin 时,浏览器会将其强制视为一个唯一的、不透明的源(opaque origin),与任何实际 URL 的源(包括父页面和自身原本的源)完全隔离
  • 在此模式下,所有与源相关的 Web 存储 API(sessionStoragelocalStorageIndexedDBCookies 等)都无法访问,因为访问它们需要明确的源标识。

image.png

3、设置 allow-same-origin

  • 当 sandbox 不存在时,同源 iframe 可以通过 contentDocument 访问父页面的 DOM。
  • 有了 sandbox 但没有 allow-same-origin,即使同源也被视为跨域,无法访问父页面。

同时添加 allow-scripts 和 allow-same-origin 会使沙箱形同虚设,因为 iframe 内的脚本可以:

  • 读取和修改父页面的 DOM
  • 窃取父页面的 localStorage、Cookie、sessionStorage
  • 发送带有用户凭据的请求
  • 执行任意操作,就像没有沙箱一样

4、设置 allow-modals

Ignored call to 'alert()'. The document is sandboxed, and the 'allow-modals' keyword is not set.

// page.html
alert("这是一个page页面");

5、设置 allow-popups

<body>
<!-- page.html -->
<h1>这是一个page页面</h1>
<button id="btn-click">点击我</button>
</body>

<script>
const btn = document.getElementById("btn-click");
btn.addEventListener("click", () => {
    window.open("https://www.baidu.com");
});
</script>

Blocked opening 'https://www.baidu.com/' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set.

6、设置 allow-forms

<form id="form-click" action="https://localhost:8443/api/user" method="post">
  <input type="text" name="name" value="lili" />
  <input type="text" name="email" value="lili@example.com" />
  <input type="submit" value="点击我" />
</form> 

Blocked form submission to 'https://localhost:8443/api/user' because the form's frame is sandboxed and the 'allow-forms' permission is not set.

loading 属性

  • lazy:延迟加载,当 iframe 进入视口附近时才加载。
  • eager:立即加载(默认)

allow 属性

allow 属性用于控制嵌入在 <iframe> 中的内容可以使用哪些浏览器功能(如地理位置、摄像头、麦克风、全屏、自动播放等)。

功能名描述
geolocation地理位置
camera摄像头
microphone麦克风
fullscreen全屏请求
payment支付请求
autoplay自动播放媒体(带声音)
encrypted-mediaEME(加密媒体扩展)
midiMIDI 设备
clipboard-read / clipboard-write剪贴板读写
usbWebUSB
bluetoothWeb Bluetooth
xr-spatial-trackingWebXR
<iframe
  allow="geolocation 'src'"
  srcdoc='
        <html>
        <body>
            <button onclick="navigator.geolocation.getCurrentPosition(pos => alert(pos.coords.latitude))">
                获取位置
            </button>
        </body>
        </html>
    '
>
</iframe>
属性作用控制对象
sandbox对 iframe 内容施加安全限制(如禁止脚本、表单、弹窗等)基本网页行为
allow控制 iframe 内容能否使用浏览器强大功能(设备 API)设备/浏览器能力

iframe 与父页面通信(同源 & 跨源)

父子页面通信/同源 (无 sandbox)

父页面 → iframe 内部

父页面可以通过 iframe.contentDocument 获取 iframe 的文档对象,进而操作其 DOM。

  • iframe.contentWindow, iframe 元素内部的 window 对象
  • iframe.contentDocument, iframe 元素内部的 document 对象
// 父页面
 <iframe src="page.html"></iframe>
 
 const iframe = document.querySelector("iframe");
 console.log('父winodw', iframe.contentWindow)
// page.html iframe 页面
 <h1>这是一个page页面</h1>
 
 console.log('page.html-window', window)

iframe 内部 → 父页面

iframe 内的脚本可以通过 window.parent.document 访问父页面的 DOM。

  • window.parent, 当前 iframe 的直接父级 window
  • window.top, 最顶层窗口(浏览器标签页的 window),多层嵌套时直达顶层

父子页面通信/同源 (有 sandbox)

如果 iframe 有 sandbox 属性(且没有 allow-same-origin),则浏览器会强制将 iframe 的内容视为唯一不透明源,即使实际 URL 与父页面同源,也会被当作跨域处理。

  • 父页面的 contentDocument 访问会因跨域安全错误而抛出异常。
  • iframe 内部也无法访问 window.parent 的 DOM。

父页面能否操作 iframe 内部,取决于同源策略,而非 sandboxsandbox(不带 allow-same-origin)会让同源 iframe 被当作跨源处理,从而阻止父页面读写 iframe 的 DOM

跨域通信

三种 sandbox 配置对比

sandboxiframe origin直接访问 DOMpostMessage targetOriginevent.origin
无 sandbox保持原始 origin同源可访问精确 origin"http://localhost:3000"
allow-scripts 和 allow-same-origin保持原始 origin同源可访问精确 origin"http://localhost:3000"
allow-scriptsopaque origin跨域拦截必须用 "*""null"

主页面

  <body>
    <h1>这是一个 顶层 页面</h1>
    <button id="sendMessageBtn">发送消息</button>
    <iframe src="parent.html" width="100%" sandbox="allow-scripts"></iframe>
  </body>
  <script>
    window.aiframeFlag = "top";
    console.log("这是一个 顶层 页面");

    const iframe = document.querySelector("iframe");
    console.log("在顶层-获取iframe.contentWindow", iframe.contentWindow);

    const sendMessageBtn = document.querySelector("button");
    sendMessageBtn.addEventListener("click", () => {
      const iframeWindow = iframe.contentWindow;
      console.log("在顶层-获取iframeWindow", iframeWindow.postMessage);
      iframeWindow.postMessage("hello iframe,i am top", "*");
    });

    // 监听 iframeWindow 发送的消息
    window.addEventListener("message", (event) => {
      // 过滤掉自己发送的消息
      if (event.source === window) return;
      console.log("在顶层-收到消息:", event.data, "来自:", event.origin);
    });
  </script>

中间层

  <body>
    <h1>这是一个parent页面</h1>
    <button id="sendMessageBtn">发送消息</button>
    <iframe
      src="sub.html"
      height="300"
      width="90%"
      sandbox="allow-scripts"
    ></iframe>
  </body>
  <script>
    window.aiframeFlag = "parent";
    console.log("这是一个parent 页面");

    const parentWindow = window.parent;
    console.log("在parent-获取parent", parentWindow);

    const sendMessageBtn = document.querySelector("button");
    sendMessageBtn.addEventListener("click", () => {
      // 发送消息到 parentWindow
      parentWindow.postMessage("hello top am parent", "*");

      const iframeWindow = iframe.contentWindow;
      iframeWindow.postMessage("hello iframe,i am parent", "*");
    });

    // 监听 parentWindow 发送的消息
    window.addEventListener("message", (event) => {
      // 过滤掉自己发送的消息
      if (event.source === window) return;
      console.log("在parent-收到消息:", event.data, "来自:", event.origin);
    });

    const iframe = document.querySelector("iframe");
    console.log("在parent-获取iframe.contentWindow", iframe.contentWindow);
  </script>

sub 页面

  <body>
    <h1>这是一个sub页面</h1>
  </body>
  <script>
    window.aiframeFlag = "sub";
    window.addEventListener("message", (event) => {
      console.log("在sub-收到消息:", event.data, "来自:", event.origin);
    });
  </script>

示例 iframe 内的表单提交后将数据传递给父页面

流程:

  • 父页面与 iframe 不同源(跨域)
  • iframe 内部包含一个表单,用户填写后提交
  • 提交后,iframe 需要将表单数据(或处理结果)发送给父页面

允许 iframe 执行脚本和提交表单sandbox 属性必须包含:

  • allow-forms:允许表单提交
  • allow-scripts:允许运行 JavaScript(用于 postMessage

父页面

  <body>
    <h1>这是一个 顶层 页面</h1>
    <button id="sendMessageBtn">发送消息</button>
    <iframe
      src="parent.html"
      width="100%"
      sandbox="allow-scripts allow-forms"
    ></iframe>
  </body>
  <script>
    window.aiframeFlag = "top";
    console.log("这是一个 顶层 页面");

    const iframe = document.querySelector("iframe");
    console.log("在顶层-获取iframe.contentWindow", iframe.contentWindow);
    const iframeWindow = iframe.contentWindow;

    const sendMessageBtn = document.querySelector("button");
    sendMessageBtn.addEventListener("click", () => {
      console.log("在顶层-获取iframeWindow", iframeWindow.postMessage);
      iframeWindow.postMessage("hello iframe,i am top", "*");
    });

    // 监听 iframeWindow 发送的消息
    window.addEventListener("message", (event) => {
      // 过滤掉自己发送的消息
      if (event.source === window) return;
      console.log("在顶层-收到消息:", event.data, "来自:", event.origin);

      iframeWindow.postMessage("已收到form 数据", "*");
    });
  </script>

子页面

拦截表单的默认提交行为(event.preventDefault()

  <body>
    <h1>这是一个parent页面</h1>

    <form id="myForm">
      <input
        type="text"
        id="input"
        name="name"
        placeholder="请输入姓名"
        value="lili"
      />
      <input
        type="text"
        id="email"
        name="email"
        placeholder="请输入邮箱"
        value="123@qq.com"
      />
      <button type="submit" id="submitBtn">发送发送消息</button>
    </form>
  </body>
  <script>
    window.aiframeFlag = "parent";
    console.log("这是一个parent 页面");

    document.getElementById("myForm").onsubmit = (e) => {
      // 阻止表单默认提交行为
      e.preventDefault();
      const formData = new FormData(e.target);
      const data = Object.fromEntries(formData.entries());
      // 发送给父页面
      window.parent.postMessage(
        {
          type: "formSubmit",
          payload: data,
        },
        "*",
      ); // 精确指定父页面源
    };

    // 监听 parentWindow 发送的消息
    window.addEventListener("message", (event) => {
      // 过滤掉自己发送的消息
      if (event.source === window) return;
      console.log("在parent-收到消息:", event.data, "来自:", event.origin);
    });
  </script>

iframe 安全(XSS、点击劫持、沙箱)

风险描述影响
点击劫持 (Clickjacking)攻击者将目标网站透明 iframe 覆盖在诱饵按钮上,诱导用户点击窃取凭证、执行非授权操作
恶意 iframe 窃取父页面信息嵌入的恶意页面通过脚本读取父页面 DOM、Cookie 等数据泄露、XSS
沙箱逃逸未正确配置 sandbox 时,恶意内容可执行脚本、提交表单钓鱼、植入恶意代码

点击劫持

点击劫持(Clickjacking,又称 “UI 覆盖攻击”)是一种针对用户视觉误导的恶意攻击,核心原理是 攻击者通过透明或半透明的 iframe 嵌套目标网站,叠加在恶意页面的诱导元素(如按钮、链接)上方,诱导用户点击看似无害的内容时,实际点击了目标网站的敏感操作按钮(如登录、支付、授权、删除),从而骗取用户执行未授权操作。

预防点击劫持

  1. 后端可以通过设置 HTTP 响应头 来禁止浏览器将当前页面嵌入到 <iframe> 中,从而有效防止点击劫持(Clickjacking)和盗链嵌入。

响应头 X-Frame-Options

效果
DENY完全禁止被任何页面嵌入(包括同域名)
SAMEORIGIN只允许被同源(相同协议+域名+端口)页面嵌入

image.png

image.png

响应头 Content-Security-Policy

指令含义
frame-ancestors 'none'禁止任何页面嵌入
frame-ancestors 'self'只允许同源页面嵌入
frame-ancestors https://example.com只允许特定域名嵌入

禁止任何域名

image.png

允许同源页面

示例 vue3 项目 proxy 代理

image.png

允许指定域名

image.png

恶意 iframe 窃取父页面信息

当网页嵌入一个恶意 iframe 时,该 iframe 可能会尝试读取父页面中的敏感数据(如 Cookie、localStorage、DOM 内容、表单输入等)。这种行为能否成功,取决于浏览器的同源策略和 iframe 的 sandbox 属性配置。

沙箱逃逸

所谓  “沙箱逃逸”  ,就是指嵌入式内容(如广告、插件、用户生成内容)突破了sandbox属性设定的安全边界,逃逸到外层父页面,甚至获取更高权限的操作系统能力。

在 HTTP 响应头中添加该指令,其基础语法如下:
Content-Security-Policy: sandbox; 或 Content-Security-Policy: sandbox <value>;

默认情况下(值为空),沙箱会启用所有限制。

  • allow-forms: 允许沙盒内的页面提交表单。
  • allow-modals: 允许页面使用 alert()confirm() 等方法打开模态窗口。
  • allow-orientation-lock: 允许页面锁定屏幕方向。
  • allow-pointer-lock: 允许页面使用指针锁定 API。
  • allow-popups: 允许打开新窗口或弹出窗口。
  • allow-popups-to-escape-sandbox: 允许新打开的窗口不受当前沙箱限制。
  • allow-same-origin: 允许沙盒中的内容维持其原始来源(⚠️ 与 allow-scripts 并用有高安全风险)。
  • allow-scripts: 允许在沙盒中执行脚本。
  • allow-top-navigation: 允许沙盒内容将顶层窗口导航到新 URL

iframe 性能优化

iframe 天生会阻塞 window.load,defer 对 iframe 无效—— 因为 defer 只对 <script> 生效,iframe 是独立文档,浏览器必须等它(及里面所有资源)加载完才会触发主页面 load。

window.load 定义:要等主页面 + 所有子资源(iframe、图片、字体、样式表、脚本) 全部加载完成才触发。

iframe 本质:是一个独立的浏览器文档,浏览器把它当作 “必须完成的子资源”,所以:

  • 只要页面里有 <iframe src="...">不管它在不在视口、是否隐藏,都会立刻发起请求
  • 主页面 load 必须等这个 iframe 连同它内部所有 JS / 图片 / CSS 全部加载完才会触发

示例 原生 iframe 懒加载

懒加载 iframe 是优化页面首屏性能的重要手段,避免非可视区域的 iframe 加载阻塞资源。

触发时机由浏览器决定(接近视口)

<p>iframe 懒加载优化</p>
<div class="iframe-content">this this is iframe</div>
<iframe loading="lazy" src="iframe.html"></iframe>

示例 手动控制延迟加载 iframe(IntersectionObserver)

利用 IntersectionObserver ,每次交叉变化都会触发加载

<iframe id="lazyFrame" data-src="iframe.html"></iframe>
 
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const iframe = entry.target;
      iframe.src = iframe.dataset.src;
    }
  });
});
observer.observe(document.getElementById("lazyFrame"));

示例 延迟加载 先占位 about:blank,再赋值 src

 <iframe id="lazyFrame" data-src="iframe.html" src="about:blank"></iframe>
 
 window.addEventListener("DOMContentLoaded", function () {
  console.log("当前时间", new Date().toLocaleString());
  console.log("加载完成");
  const iframe = document.getElementById("lazyFrame");
  iframe.src = iframe.dataset.src;
});

DOMContentLoaded 早触发,只等 DOM 解析完成;load 晚触发,要等页面所有资源(DOM、样式、脚本、图片、iframe、音视频)全部加载完毕。 页面从上到下解析、加载的完整时序:

  1. 开始解析 HTML,构建 DOM 树
  2. 遇到样式表,构建 CSSOM 树
  3. DOM + CSSOM 合成 渲染树
  4. DOMContentLoadedDOM 树构建完成 立即触发(不等图片、iframe、大资源)
  5. 继续加载图片、视频、iframe、字体等外部子资源
  6. load页面所有资源全部加载、解析、渲染完毕 才触发
<iframe id="lazyFrame" data-src="iframe.html" src="about:blank"></iframe>
window.addEventListener("load", function () {
  console.log("当前时间", new Date().toLocaleString());
  console.log("加载完成");
  const iframe = document.getElementById("lazyFrame");
  iframe.src = iframe.dataset.src;
});

示例 延迟创建 iframe

window.addEventListener("load", function () {
  console.log("当前时间", new Date().toLocaleString());
  console.log("加载完成");

  const iframe = document.createElement("iframe");
  iframe.id = "lazyFrame";
  iframe.src = "iframe.html";
  iframe.style.display = "none";
  iframe.onload = function () {
    console.log("iframe加载完成");
    iframe.style.display = "block";
  };
  document.body.appendChild(iframe);
});

示例 路由同步

模拟 单页应用里 iframe 路由与父页面路由联动(通过 postMessage

  • 利用 history.pushState 无刷新
  • 利用 popstate 事件

主页面(父页面)

  <body>
    <h1>Parent Page</h1>
    <iframe id="childFrame" src="child.html" width="800" height="400"></iframe>
    <div>当前父路由:<span id="parentRoute"></span></div>

    <script>
      const iframe = document.getElementById("childFrame");
      let isSyncing = false; // 防止循环同步

      // 更新父页面显示
      function updateParentDisplay(path) {
        document.getElementById("parentRoute").innerText = path;
      }

      // 父路由变化时通知 iframe
      function notifyIframe(path) {
        if (isSyncing) return;
        if (iframe.contentWindow) {
          iframe.contentWindow.postMessage(
            {
              type: "ROUTE_CHANGE",
              path: path,
            },
            "*",
          ); // 生产环境请指定目标 origin
        }
      }

      // 监听父页面自身路由变化(popstate 或 pushState)
      function onParentRouteChange() {
        const path = window.location.pathname; // 或使用 hash
        updateParentDisplay(path);
        notifyIframe(path);
      }

      // 监听 iframe 发来的路由变化
      window.addEventListener("message", (event) => {
        // 生产环境请验证 event.origin
        if (event.data && event.data.type === "IFRAME_ROUTE_CHANGE") {
          isSyncing = true;
          const newPath = event.data.path;
          // 更新父页面 URL,不触发 popstate(使用 replaceState 或 pushState)
          if (window.location.pathname !== newPath) {
            history.pushState(null, "", newPath);
            updateParentDisplay(newPath);
          }
          isSyncing = false;
        }
      });

      // 监听 popstate(浏览器前进后退)
      window.addEventListener("popstate", () => {
        onParentRouteChange();
      });

      // 初始化:首次加载同步
      iframe.onload = () => {
        onParentRouteChange();
      };
    </script>
  </body>

iframe 子页面

  <body>
    <h2>Child Iframe</h2>
    <div>当前子路由:<span id="childRoute"></span></div>
    <button onclick="navigate('/page1')">Go to Page 1</button>
    <button onclick="navigate('/page2')">Go to Page 2</button>

    <script>
      let isSyncing = false;

      // 更新 iframe 内部显示
      function updateChildDisplay(path) {
        document.getElementById("childRoute").innerText = path;
      }

      // 主动改变 iframe 路由(并通知父页面)
      function navigate(path) {
        if (isSyncing) return;
        if (window.location.pathname !== path) {
          history.pushState(null, "", path);
          updateChildDisplay(path);
          notifyParent(path);
        }
      }

      // 通知父页面路由变化
      function notifyParent(path) {
        window.parent.postMessage(
          {
            type: "IFRAME_ROUTE_CHANGE",
            path: path,
          },
          "*",
        ); // 生产环境指定父页面 origin
      }

      // 监听父页面发来的路由变化
      window.addEventListener("message", (event) => {
        // 生产环境请验证 event.origin
        if (event.data && event.data.type === "ROUTE_CHANGE") {
          const newPath = event.data.path;
          if (window.location.pathname !== newPath) {
            isSyncing = true;
            history.pushState(null, "", newPath);
            updateChildDisplay(newPath);
            isSyncing = false;
          }
        }
      });

      // 监听 iframe 内自身路由变化(popstate)
      window.addEventListener("popstate", () => {
        const path = window.location.pathname;
        updateChildDisplay(path);
        notifyParent(path);
      });

      // 初始化显示
      updateChildDisplay(window.location.pathname);
    </script>
  </body>

回顾 IntersectionObserver API

IntersectionObserver(交叉观察器)用于异步检测目标元素与根容器(视口 / 指定父容器)是否发生可视区域交叉,常用来实现懒加载、触底加载、曝光埋点、动画触发,也是解决 iframe 提前加载阻塞 load 的优选方案。

const observer = new IntersectionObserver(callback, options);
interface IntersectionObserver {
  constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options = {});
  readonly attribute (Element or Document)? root;
  readonly attribute DOMString rootMargin;
  readonly attribute DOMString scrollMargin;
  readonly attribute FrozenArray<double> thresholds;
  undefined observe(Element target);
  undefined unobserve(Element target);
  undefined disconnect();
  sequence<IntersectionObserverEntry> takeRecords();
};

第二个参数 option

interface IntersectionObserverInit {
    root?: Element | Document | null;
    rootMargin?: string;
    scrollMargin?: string;
    threshold?: number | number[];
}

阈值(threshold)的工作原理

// 默认 threshold: [0]
// 回调触发时机:交叉比例跨越 0 时(进入 / 离开)

new IntersectionObserver(callback, {
  threshold: [0, 0.5, 1.0]
});
// 回调触发时机:
//   交叉比例跨越 0   时 → 进入/离开视口
//   交叉比例跨越 0.5 时 → 可见面积达到 50%
//   交叉比例跨越 1.0 时 → 完全可见
threshold触发时机
[0](默认)进入视口 / 离开视口
[0.5]可见面积达到 50% / 从 50% 以上降到 50% 以下
[1.0]完全可见 / 从完全可见变为部分不可见
[0, 0.25, 0.5, 0.75, 1.0]每跨越一个 25% 边界都触发

每个阈值都是双向的——从下往上跨越和从上往下跨越都会触发回调。

回调函数 callback(entries, observer)

  • entries:数组,状态发生变化的元素集合(批量触发)
  • observer:当前观察器实例
interface IntersectionObserverEntry {
  constructor(IntersectionObserverEntryInit intersectionObserverEntryInit);
  readonly attribute DOMHighResTimeStamp time;
  readonly attribute DOMRectReadOnly? rootBounds;
  readonly attribute DOMRectReadOnly boundingClientRect;
  readonly attribute DOMRectReadOnly intersectionRect;
  readonly attribute boolean isIntersecting;
  readonly attribute double intersectionRatio;
  readonly attribute Element target;
};
entry.target // 被监听的 DOM 元素 
entry.isIntersecting // 布尔值:元素是否进入可视区(最常用) 
entry.intersectionRatio // 交叉比例 0 ~ 1(0=完全不可见,1=完全可见) 
entry.boundingClientRect // 目标元素位置尺寸 
entry.rootBounds // 根容器位置尺寸 
entry.intersectionRect // 交叉区域尺寸

image.png

最后

  1. iframe HTML 规范
  2. IntersectionObserver W3C 标准
  3. portal 规范