前端跨域 iframe 通信别再手写 postMessage:用 iframe-js 实现 ACK、RPC 与状态同步

📅 2026/8/2 4:27:40 👁️ 阅读次数 📝 编程学习
前端跨域 iframe 通信别再手写 postMessage:用 iframe-js 实现 ACK、RPC 与状态同步

多年前,我因为项目中频繁遇到 iframe 跨域通信问题,写了一个小工具来封装postMessage。后来这个工具逐渐完善,最终开源成了现在的iframe-js

这是我第一次认真写文章介绍这个项目。过去三年里,它从最初只能发送消息,逐步增加了 ACK 确认、握手协议、离线消息队列、RPC 远程调用、状态同步和 iframe 高度自适应等能力。

这篇文章不想单纯介绍一个 npm 包,而是想分享我在解决 iframe 通信问题时遇到的痛点,以及iframe-js是如何一步步演变出来的。如果你也在处理跨域 iframe 通信,希望它能帮你少踩一些坑。

在前端项目中,iframe 经常被用于:

  • 嵌入第三方页面
  • 微前端子应用
  • 支付、登录、客服等独立模块
  • Vue、React 项目之间的页面通信

最常见的通信方式是原生window.postMessage。但随着业务变复杂,通常会遇到几个问题:

  1. 消息发送时,iframe 还没有加载完成,导致消息丢失;
  2. 发送消息后,不知道对方是否真正收到;
  3. 事件监听和回调越来越多,代码容易变成“回调地狱”;
  4. 跨域 iframe 高度无法自动适应;
  5. 多个 iframe 同时存在时,消息容易互相干扰;
  6. 缺少 Origin 白名单校验,存在安全隐患。

于是我开源了一个轻量的 iframe 跨域通信库:iframe-js

项目地址:

  • npm:https://www.npmjs.com/package/iframe-js
  • GitHub:GitHub - 1503963513/iFramejs: 解决 IFrame 通信;优雅使用 PostMessage · GitHub
  • 在线 Demo:iFrame.js - Parent Page Demo

安装

npm install iframe-js

也可以使用 pnpm 或 yarn:

pnpm add iframe-js

基础用法

父页面

import Iframe from 'iframe-js'; const iframe = new Iframe({ container: document.querySelector('#child-frame'), url: 'https://child.example.com/index.html', whiteList: ['https://child.example.com'], timeout: 5000 }); // 监听子页面事件 iframe.action('childReady', (event) => { console.log('子页面已准备好:', event.data); }); // 向子页面发送事件 iframe.emit('hello', { message: '你好,子页面' });

页面中准备一个 iframe 容器:

<iframe id="child-frame"></iframe>

子页面

import Iframe from 'iframe-js'; const childApp = new Iframe('child-frame'); childApp.addWhiteList('https://parent.example.com'); // 监听父页面发送的事件 childApp.action('hello', (event) => { console.log('收到父页面消息:', event.data); childApp.emit('childReady', { status: 'success' }); });

Promise ACK 确认机制

原生postMessage发送后,默认无法知道消息是否被对方接收。

iframe-js提供了 Promise 风格的 ACK API:

const success = await iframe.emitToChildWithAck( 'requestPayment', { amount: 100 }, 8000 ); if (success) { console.log('子页面已确认收到消息'); } else { console.log('发送超时或目标页面未响应'); }

如果 iframe 尚未加载完成,消息会暂存在内部队列中,等握手成功后自动发送。

RPC 远程调用

除了发送事件,还可以像调用本地函数一样调用 iframe 中的远程函数。

父页面暴露方法:

iframe.expose('getUserInfo', async ({ id }) => { const response = await fetch(`/api/user/${id}`); return response.json(); });

子页面调用:

const userInfo = await childApp.callRemote( 'getUserInfo', { id: 1001 }, 5000 ); console.log(userInfo);

这对于微前端、嵌入式业务模块和跨页面服务调用非常方便。

状态同步

父页面可以向子页面同步全局状态:

iframe.setState({ theme: 'dark', language: 'zh-CN' }); iframe.setState({ theme: 'light' });

子页面监听状态变化:

childApp.onStateChange((newState, oldState) => { console.log('状态发生变化:', newState); });

同一同步帧内多次调用setState会自动合并,减少跨域通信次数。

iframe 高度自动适应

跨域 iframe 的高度自适应一直比较麻烦,iframe-js内置了自动高度同步能力。

父页面:

iframe.enableAutoResize();

子页面:

childApp.startAutoResizer({ target: 'body', offset: 20 });

当子页面内容发生变化时,父页面中的 iframe 高度会自动调整,可以避免出现双滚动条。

安全白名单

默认情况下,建议明确配置允许通信的 Origin:

const iframe = new Iframe({ container: document.querySelector('#child-frame'), url: 'https://child.example.com/index.html', whiteList: [ 'https://child.example.com' ] });

也可以动态管理白名单:

iframe.addWhiteList('https://trusted.example.com'); iframe.removeWhiteList('https://trusted.example.com'); console.log(iframe.getWhiteList());

开发环境可以使用*,但生产环境不建议这样配置:

iframe.addWhiteList('*');

如果允许任意来源通信,恶意网站可能向 iframe 发送伪造消息,因此不要在通配符环境下传递敏感 Token 或隐私数据。

核心特性

目前iframe-js主要支持:

  • 跨域 iframe 双向通信
  • Promise ACK 确认机制
  • 离线消息队列
  • 握手协议
  • RPC 远程函数调用
  • 全局状态同步
  • 自动高度适应
  • 多 iframe 实例隔离
  • Origin 白名单校验
  • Vue、React 以及原生 JavaScript
  • TypeScript 类型声明
  • 零运行时依赖

iframe-js的整体架构和通信链路

在线体验

可以直接打开 Demo 查看通信过程:

  • 基础通信与 ACK:iFrame.js - Parent Page Demo
  • 自动高度适应:iFrame.js - Parent Page Demo
  • 状态同步与 RPC:iFrame.js - Parent Page Demo

建议打开浏览器控制台,可以看到握手、消息队列和 ACK 回执等通信日志。

总结

如果项目中只是偶尔发送一条消息,原生postMessage已经够用。

但当你需要处理以下场景时:

  • 消息可靠送达
  • iframe 加载时序
  • 跨域 RPC
  • 状态共享
  • 自动高度
  • 多实例隔离
  • 安全白名单

可以尝试使用iframe-js,减少重复编写通信基础设施代码。

这是我第一次在 CSDN 分享iframe-js。项目还有很多可以改进的地方,也可能存在没有覆盖到的业务场景。

如果你正在使用,欢迎提 Issue 或留下建议;如果这个项目对你有帮助,也欢迎点一个 Star。

GitHub - 1503963513/iFramejs: 解决 IFrame 通信;优雅使用 PostMessage · GitHub