多年前,我因为项目中频繁遇到 iframe 跨域通信问题,写了一个小工具来封装postMessage。后来这个工具逐渐完善,最终开源成了现在的iframe-js。
这是我第一次认真写文章介绍这个项目。过去三年里,它从最初只能发送消息,逐步增加了 ACK 确认、握手协议、离线消息队列、RPC 远程调用、状态同步和 iframe 高度自适应等能力。
这篇文章不想单纯介绍一个 npm 包,而是想分享我在解决 iframe 通信问题时遇到的痛点,以及iframe-js是如何一步步演变出来的。如果你也在处理跨域 iframe 通信,希望它能帮你少踩一些坑。
在前端项目中,iframe 经常被用于:
- 嵌入第三方页面
- 微前端子应用
- 支付、登录、客服等独立模块
- Vue、React 项目之间的页面通信
最常见的通信方式是原生window.postMessage。但随着业务变复杂,通常会遇到几个问题:
- 消息发送时,iframe 还没有加载完成,导致消息丢失;
- 发送消息后,不知道对方是否真正收到;
- 事件监听和回调越来越多,代码容易变成“回调地狱”;
- 跨域 iframe 高度无法自动适应;
- 多个 iframe 同时存在时,消息容易互相干扰;
- 缺少 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