如何优雅的实现 iframe 多层级嵌套通讯

前言

在前端开发项目中,不可避免的总会和 iframe 进行打交道,我们通常会使用 postMessage 实现消息通讯。

如果存在下面情况:

  • iframe 父子通讯
  • iframe 同层级通讯
  • iframe 嵌套层级通讯

当面对这种复杂的情况的时候,通讯不可避免成为复杂问题。

快速开始

为了解决这复杂的问题,我开发了 iframe-bridge 来帮助大家优雅的解决这类问题。

bash 复制代码
npm install bridge-iframe
# pnpm
pnpm install bridge-iframe
# yarn
yarn add bridge-iframe

假设页面层级如下:

  • Main
    • Main/Node1

主页面(Main)

html 复制代码
<h1>Main</h1>
<iframe src="Node1.html" id="Node1"></iframe>
javascript 复制代码
import { IFrameBridge, IFrameMessage } from 'bridge-iframe';

// 创建桥接对象
const bridge = new IFrameBridge;
// 连接直接下属节点 Node1 关联 iframe 窗口
birdge.ifrme('Node1', document.getElementById('Node1'));

// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {
	vo.getData(); // 获取请求数据
	vo.getResult(); // 获取响应数据
	return '来自于 Main';
});

// 等待桥接初始化完成
birdge.ready(async () => {
	console.log('Main 初始化完成!!!');
});

// 等待 Node1 节点桥接完成
birdge.ready('Node1', async () => {
	console.log('Watch Node1 初始化完成!!!');

	// 请求 Node1 的 say 方法
	birdge.request({
		name: 'Node1',
		method: 'say',
	}).then((vo: any) => {
		console.log('在 Main 中请求 Node1.say 方法', vo);
	}).catch((err: any) => {
		console.log('出现错误', err);
	});
});

// 窗口销毁时
bridge.destroy();

子页面(Node1)

html 复制代码
<h1>Node1</h1>
javascript 复制代码
import { IFrameBridge } from 'bridge-iframe';

// 创建桥接对象
const bridge = new IFrameBridge({ name: 'Node1' });

// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {
	return '来自于 Nodeq';
});

// 等待桥接初始化完成
birdge.ready(async () => {
	console.log('Node1 初始化完成!!!');
});

// 等待 Node1 节点桥接完成
birdge.ready('Main', async () => {
	console.log('Watch Main 初始化完成!!!');

	// 请求 Main 的 say 方法
	birdge.request({
		name: 'Main',
		method: 'say',
	}).then((vo: any) => {
		console.log('在 Node1 中请求 Main.say 方法', vo);
	}).catch((err: any) => {
		console.log('出现错误', err);
	});
});

// 窗口销毁时
bridge.destroy();

其中关于请求 name 在这里称呼为 iframe node域名 作为通讯标识。

关于子节点的名称可以为任意名称,但有两类名称是内置的代表特殊作用不能被使用。

  • Main 作为 主节点/主窗口 的名称地址
  • Parent 作为只请求上一级节点的名称标识,不管上层节点名字是什么

假设页面层级如下(更复杂):

  • Main

    • Main/Node1
      • Main/Node1/Node1-1
      • Main/Node1/Node1-2
    • Main/Node2
      • Main/Node2/Node2-1
      • Main/Node2/Node2-2
  • 在这里还是一样的,创建主页面桥接对象,并关联子页面 iframe 相对的子页面也创建有名称的桥接对象。

  • 还是通过注册一些可以被其他节点调用的方法来实现双通讯的。

实现原理

这里参考了计算机网络的 交换机 的模式来实现跨层级转发。

网络模型

bash 复制代码
                         /------> (子节点1)
(父节点) <---------> (节点) <------------------> (子节点2)
                         \------> (子节点n) ...
  • 每个 节点 都有 上级节点x1下级节点xN 的结构。
  • 消息通讯的核心本质还是 postMessage 来实现。
  • 当消息经过 节点 的时候,通过 message.path 判断 message 是向上 window.parent.postMessage() 传递还是向下 iframe.contentWindow.postMessage() 传递。
  • 当消息经过 节点 的时候,会记录经过的路径为 tracks{ 节点名称, 转发方向 }[] 以此来实现初始地址分配,以及消息返回路径确认。

系统协议

为了实现跨层级通讯,动态为 节点 分配地址,得实现 节点名称映射地址库 来实现。

  • 主窗口/页面提供如下内置方法:
    • @bridge/online 通知主窗口注册地址
    • @bridge/domain 获取名称映射地址
    • @bridge/mapping 获取所有映射地址
  • 所有窗口/页面提供如下内置方法:
    • @bridge/ready 节点准备好了吗?

为了方便调用,定义了如下内置地址:

  • Main 请求主窗口地址
  • Parent 向上级请求窗口(无论层级高低都向上级请求)

通讯模拟:

页面层级

  • Main
    • Main/Node1
      • Main/Node1/Node1-1
      • Main/Node1/Node1-2
    • Main/Node2
      • Main/Node2/Node2-1
      • Main/Node2/Node2-2

向上请求 Main/Node1/Node1-1Main

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↑↑↑Main
    • tracks[{Node1-1:U}, {Node1:U}]
  • Main 处理逻辑
  • Main 响应 ↓↓↓Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↓↓↓Main/Node1/Node1-1
    • tracks[]
  • Main/Node1/Node1-1 收到响应

向下请求 MainMain/Node1/Node1-1

  • <内置协议获取地址>
  • Main 请求 ↓↓↓Main/Node1
    • tracks[{Main:D}]
  • Main/Node1 转发 ↓↓↓Main/Node1/Node1-1
    • tracks[{Main:D}, {Node1:D}]
  • Main/Node1/Node1-1 处理逻辑
  • Main/Node1/Node1-1 响应 ↑↑↑Main/Node1
    • tracks[{Main:D}]
  • Main/Node1 转发 ↑↑↑Main
    • tracks[]
  • Main 收到响应

同级请求 Main/Node1/Node1-1Main/Node1/Node1-2

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↓↓↓Main/Node1/Node1-2
    • tracks[{Node1-1:U}, {Node1:D}]]
  • Main/Node1/Node1-2 处理逻辑
  • Main/Node1/Node1-2 响应 ↑↑↑Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↓↓↓Main/Node1/Node1-1
    • tracks[]
  • Main/Node1/Node1-1 收到响应

跨级请求 Main/Node1/Node1-1Main/Node2/Node2-1

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↑↑↑Main
    • tracks[{Node1-1:U}, {Node1:U}]
  • Main 转发 ↓↓↓Main/Node2
    • tracks[{Node1-1:U}, {Node1:U}, {Main:D}]
  • Main/Node2 转发 ↓↓↓Main/Node2/Node2-1
    • tracks[{Node1-1:U}, {Node1:U}, {Main:D}, {Node2:D}]
  • Main/Node2/Node2-1 处理逻辑
  • Main/Node2/Node2-1 响应 ↑↑↑Main/Node2
    • tracks[{Node1-1:U}, {Node1:U}, {Main:D}]
  • Main/Node2 转发 ↑↑↑Main
    • tracks[{Node1-1:U}, {Node1:U}]
  • Main 转发 ↓↓↓Main/Node1
    • tracks[{Node1-1:U}]
  • Main/Node1 转发 ↓↓↓Main/Node1/Node1-1
    • tracks[]
  • Main/Node1/Node1-1 收到响应
相关推荐
ZwaterZ28 分钟前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
ZwaterZ3 小时前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
木子七5 小时前
vue2-vuex
前端·vue
小小黑0076 小时前
uniapp+vue3+ts H5端使用Quill富文本插件以及解决上传图片反显的问题
uni-app·vue
Ztiddler9 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue
三天不学习9 小时前
前端工程化-node/npm/babel/polyfill/webpack 一文速通
前端·webpack·npm
逆旅行天涯15 小时前
【功能实现】bilibili顶部鼠标跟随效果怎么实现?
前端·javascript·vue
水w1 天前
Node.js windows版本 下载和安装(详细步骤)
开发语言·前端·windows·npm·node
DDDHL_1 天前
vitepress博客模板搭建
前端·vue
guokanglun1 天前
npm镜像查看和修改
前端·npm·node.js