h5-iframe-ipc
v1.0.5
Published
一个轻量级的H5与iframe通信SDK,支持双向消息传递和事件监听
Maintainers
Readme
h5-iframe-ipc
一个轻量级、类型安全的 H5 与 iframe 双向通信 SDK
✨ 特性
- 🚀 轻量级:零依赖,gzip 后仅 ~2KB
- 🔒 类型安全:完整的 TypeScript 类型定义
- 📡 双向通信:支持父页面 ↔ 子页面(H5)双向消息传递
- 🎯 Promise 支持:invoke 方法支持 Promise,轻松处理异步操作
- 📢 事件系统:内置事件监听和发射机制(on/off/emit)
- 🧹 资源管理:提供 destroy 方法,避免内存泄漏
- 🔧 简单易用:API 设计简洁直观,5 分钟快速上手
📦 安装
# npm
npm install h5-iframe-ipc
# yarn
yarn add h5-iframe-ipc
# pnpm
pnpm add h5-iframe-ipc🚀 快速开始
H5 子页面(在 iframe 内运行)
H5 页面使用 IPCH5 类,运行在 iframe 内部。
import { IPCH5 } from 'h5-iframe-ipc';
// 创建 IPC 实例
const ipc = new IPCH5();
// 注册方法供父页面调用
ipc.handle('getUserInfo', (params) => {
return {
name: '张三',
age: 25,
email: '[email protected]'
};
});
// 监听来自父页面的事件
ipc.on('parentReady', (data) => {
console.log('父页面已就绪:', data);
});
// 向父页面发送事件
ipc.emit('h5Loaded', {
timestamp: Date.now(),
version: '1.0.0'
});iframe 父页面(包含 iframe 标签)
父页面使用 IPCIframe 类,需要传入 iframe 的 contentWindow。
import { IPCIframe } from 'h5-iframe-ipc';
// 获取 iframe 元素
const iframe = document.getElementById('myIframe');
// 创建 IPC 实例(传入 iframe 的 contentWindow)
const ipc = new IPCIframe(iframe.contentWindow);
// 调用 H5 注册的方法(支持 async/await)
async function fetchUserInfo() {
try {
const userInfo = await ipc.invoke('getUserInfo', {
userId: '12345'
});
console.log('用户信息:', userInfo);
} catch (error) {
console.error('获取用户信息失败:', error);
}
}
// 监听来自 H5 的事件
ipc.on('h5Loaded', (data) => {
console.log('H5 页面已加载:', data);
});
// 向 H5 发送事件
ipc.emit('parentReady', {
status: 'ready',
version: '1.0.0'
});📖 API 文档
IPCH5
H5 子页面(运行在 iframe 内)使用的 IPC 实例。
构造函数
const ipc = new IPCH5();无需传参,自动通过 window.parent 与父页面通信。
方法
handle(channel: string, callback: Function): void
注册一个方法,供父页面通过 invoke 调用。
参数:
channel- 方法通道名称callback- 回调函数,可以返回任意值或 Promise
示例:
// 同步返回
ipc.handle('getConfig', (params) => {
return { theme: 'dark', language: 'zh-CN' };
});
// 异步返回(Promise)
ipc.handle('fetchData', async (params) => {
const response = await fetch('/api/data');
return response.json();
});on(eventName: string, callback: Function): void
监听来自父页面的事件。
参数:
eventName- 事件名称callback- 事件处理函数
示例:
ipc.on('themeChanged', (data) => {
console.log('主题已切换:', data);
});off(eventName: string, callback?: Function): void
移除事件监听。
参数:
eventName- 事件名称callback- (可选)要移除的具体回调函数,不传则移除该事件的所有监听
示例:
const handler = (data) => console.log(data);
// 添加监听
ipc.on('myEvent', handler);
// 移除特定监听
ipc.off('myEvent', handler);
// 移除该事件的所有监听
ipc.off('myEvent');emit(eventName: string, params: any): void
向父页面发送事件。
参数:
eventName- 事件名称params- 事件数据
示例:
ipc.emit('userAction', {
type: 'click',
target: 'submitButton'
});destroy(): void
销毁 IPC 实例,清理所有监听器和处理器。
示例:
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
ipc.destroy();
});IPCIframe
iframe 父页面使用的 IPC 实例。
构造函数
const iframe = document.getElementById('myIframe');
const ipc = new IPCIframe(iframe.contentWindow);参数:
win- iframe 的 contentWindow 对象
示例:
// 方式一:通过 getElementById
const iframe = document.getElementById('myIframe');
const ipc = new IPCIframe(iframe.contentWindow);
// 方式二:通过 querySelector
const iframe = document.querySelector('#myIframe');
const ipc = new IPCIframe(iframe.contentWindow);
// 方式三:监听 iframe 加载后初始化
const iframe = document.getElementById('myIframe');
iframe.onload = () => {
const ipc = new IPCIframe(iframe.contentWindow);
};方法
invoke(channel: string, params: any): Promise<any>
调用 H5 注册的方法,返回 Promise。
参数:
channel- 方法通道名称params- 传递的参数
返回:
Promise<any>- 返回 H5 处理函数的结果
示例:
// 基本用法
const result = await ipc.invoke('getData', { id: 123 });
// 错误处理
try {
const user = await ipc.invoke('getUserInfo', { userId: '456' });
console.log(user);
} catch (error) {
console.error('调用失败:', error);
}on(eventName: string, callback: Function): void
监听来自 H5 的事件。
参数:
eventName- 事件名称callback- 事件处理函数
示例:
ipc.on('h5Ready', (data) => {
console.log('H5 已就绪:', data);
});off(eventName: string, callback?: Function): void
移除事件监听。
参数:
eventName- 事件名称callback- (可选)要移除的具体回调函数
示例:
const handler = (data) => console.log(data);
// 添加监听
ipc.on('update', handler);
// 移除特定监听
ipc.off('update', handler);
// 移除所有监听
ipc.off('update');emit(eventName: string, params: any): void
向 H5 发送事件。
参数:
eventName- 事件名称params- 事件数据
示例:
ipc.emit('notification', {
type: 'success',
message: '操作成功'
});destroy(): void
销毁 IPC 实例,清理所有监听器和待处理的 Promise。
示例:
// 组件卸载时清理
componentWillUnmount() {
ipc.destroy();
}🎯 完整示例
场景:用户登录流程
H5 子页面(login.html - 运行在 iframe 内)
import { IPCH5 } from 'h5-iframe-ipc';
class LoginPage {
constructor() {
this.ipc = new IPCH5();
this.init();
}
init() {
// 注册登录方法供父页面调用
this.ipc.handle('login', async (credentials) => {
const { username, password } = credentials;
// 调用后端 API
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (!response.ok) {
throw new Error('登录失败');
}
const result = await response.json();
// 通知父页面登录成功
this.ipc.emit('loginSuccess', result.user);
return result;
});
// 注册获取配置方法
this.ipc.handle('getLoginConfig', () => {
return {
enableSocialLogin: true,
supportedProviders: ['wechat', 'alipay'],
locale: 'zh-CN'
};
});
// 监听父页面的主题变化
this.ipc.on('themeChanged', (theme) => {
console.log('主题已切换:', theme);
this.applyTheme(theme);
});
// 监听初始数据
this.ipc.on('initData', (data) => {
console.log('接收到初始数据:', data);
this.applyTheme(data.theme);
});
// 通知父页面 H5 已就绪
this.ipc.emit('h5Ready', {
version: '1.0.0',
timestamp: Date.now()
});
}
applyTheme(theme) {
document.body.setAttribute('data-theme', theme);
}
destroy() {
this.ipc.destroy();
}
}
// 创建实例
const loginPage = new LoginPage();
// 绑定表单提交
document.querySelector('#loginForm')?.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.querySelector('#username').value;
const password = document.querySelector('#password').value;
// 可以在 H5 内部直接调用注册的方法,也可以等父页面调用
try {
const result = await loginPage.ipc.invoke('login', { username, password });
console.log('登录成功:', result);
} catch (error) {
console.error('登录失败:', error);
}
});iframe 父页面(index.html - 包含 iframe)
<!DOCTYPE html>
<html>
<head>
<title>父页面</title>
</head>
<body>
<!-- iframe 嵌入 H5 页面 -->
<iframe id="loginIframe" src="login.html" width="100%" height="600px"></iframe>
<script type="module">
import { IPCIframe } from 'h5-iframe-ipc';
class ParentPage {
constructor() {
this.iframe = document.getElementById('loginIframe');
this.initIframe();
}
initIframe() {
// 等待 iframe 加载完成
this.iframe.onload = () => {
// 创建 IPC 实例
this.ipc = new IPCIframe(this.iframe.contentWindow);
this.init();
};
}
async init() {
// 获取 H5 的配置
const config = await this.ipc.invoke('getLoginConfig', {});
console.log('H5 配置:', config);
// 监听 H5 就绪事件
this.ipc.on('h5Ready', (data) => {
console.log('H5 已就绪:', data);
// 向 H5 发送初始数据
this.ipc.emit('initData', {
theme: 'light',
apiUrl: 'https://api.example.com'
});
});
// 监听登录成功事件
this.ipc.on('loginSuccess', (user) => {
console.log('用户登录成功:', user);
this.updateUserInfo(user);
});
// 可以从父页面主动调用 H5 的登录方法
this.bindParentLoginButton();
}
bindParentLoginButton() {
document.getElementById('parentLoginBtn')?.addEventListener('click', async () => {
try {
const result = await this.ipc.invoke('login', {
username: 'testuser',
password: 'password123'
});
console.log('通过父页面调用登录成功:', result);
} catch (error) {
console.error('登录失败:', error);
}
});
}
updateUserInfo(user) {
// 更新父页面 UI
document.getElementById('username').textContent = user.name;
}
changeTheme(theme) {
// 通知 H5 切换主题
this.ipc.emit('themeChanged', theme);
}
destroy() {
this.ipc.destroy();
}
}
// 创建实例
const parentPage = new ParentPage();
</script>
</body>
</html>🔧 TypeScript 支持
本 SDK 使用 TypeScript 编写,提供完整的类型定义。
import { IPCH5, IPCIframe, IPCType } from 'h5-iframe-ipc';
// H5 子页面
const h5Ipc: IPCH5 = new IPCH5();
// 自定义类型
interface UserInfo {
name: string;
age: number;
email: string;
}
h5Ipc.handle('getUserInfo', async (): Promise<UserInfo> => {
return {
name: '张三',
age: 25,
email: '[email protected]'
};
});
// iframe 父页面
const iframe = document.getElementById('myIframe') as HTMLIFrameElement;
const parentIpc: IPCIframe = new IPCIframe(iframe.contentWindow!);
// 调用时可以指定返回类型
const userInfo = await parentIpc.invoke<UserInfo>('getUserInfo', {});
console.log(userInfo.name); // TypeScript 会有类型提示⚠️ 注意事项
1. iframe 加载时机
必须等待 iframe 加载完成后再创建 IPCIframe 实例:
// ✅ 正确
const iframe = document.getElementById('myIframe');
iframe.onload = () => {
const ipc = new IPCIframe(iframe.contentWindow);
};
// ❌ 错误 - iframe 可能还未加载完成
const iframe = document.getElementById('myIframe');
const ipc = new IPCIframe(iframe.contentWindow); // contentWindow 可能为 null2. 内存泄漏
在组件或页面销毁时,务必调用 destroy() 方法清理资源:
// React 示例
useEffect(() => {
const iframe = document.getElementById('myIframe');
let ipc;
iframe.onload = () => {
ipc = new IPCIframe(iframe.contentWindow);
};
return () => {
ipc?.destroy(); // 清理
};
}, []);
// Vue 3 示例
import { onMounted, onUnmounted } from 'vue';
let ipc = null;
onMounted(() => {
const iframe = document.getElementById('myIframe');
iframe.onload = () => {
ipc = new IPCIframe(iframe.contentWindow);
};
});
onUnmounted(() => {
ipc?.destroy();
});3. 错误处理
invoke 方法返回 Promise,务必处理可能的错误:
try {
const result = await ipc.invoke('someMethod', params);
// 处理成功结果
} catch (error) {
console.error('调用失败:', error);
// 错误处理逻辑
}4. 事件监听器管理
避免重复添加相同的监听器,必要时使用 off 方法移除:
const handler = (data) => console.log(data);
// 添加前先移除(确保不会重复添加)
ipc.off('myEvent', handler);
ipc.on('myEvent', handler);5. IPCIframe 构造函数参数
创建 IPCIframe 实例时,必须传入 iframe 的 contentWindow:
// ✅ 正确
const iframe = document.getElementById('myIframe');
const ipc = new IPCIframe(iframe.contentWindow);
// ❌ 错误
const ipc = new IPCIframe(window); // 这是错误的
const ipc = new IPCIframe(window.parent); // 这也是错误的🏗️ 架构说明
┌─────────────────────────────────────┐
│ iframe 父页面 (Parent) │
│ │
│ import { IPCIframe } │
│ const ipc = new IPCIframe( │
│ iframe.contentWindow │
│ ) │
│ │
│ ┌─────────────────────────────┐ │
│ │ <iframe src="h5.html"> │ │
│ │ │ │
│ │ H5 子页面 (Child) │ │
│ │ │ │
│ │ import { IPCH5 } │ │
│ │ const ipc = new IPCH5() │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
通信流程:
父页面 → ipc.invoke() → H5.handle()
父页面 ← H5.emit() ← H5 子页面
父页面 → ipc.emit() → H5.on()
父页面 ← ipc.on() ← H5.emit()🌐 浏览器兼容性
支持所有支持 postMessage API 的现代浏览器:
- ✅ Chrome / Edge (Chromium) >= 60
- ✅ Firefox >= 55
- ✅ Safari >= 11
- ✅ iOS Safari >= 11
- ✅ Android WebView >= 60
📚 相关文档
🐛 问题反馈
如果你发现任何问题或有改进建议,欢迎提交 Issue:
🤝 贡献
欢迎提交 Pull Request!在提交之前,请确保:
- 代码通过 TypeScript 编译
- 遵循项目代码风格
- 添加必要的注释
📄 许可证
MIT © hehongyu
🔗 相关链接
如果这个项目对你有帮助,欢迎 ⭐ Star 支持一下!
