当前位置: 首页 > news >正文

Vue中iFrame跨窗口通信实现与源码解析

一、整体架构设计

本系统通过Vue3实现iFrame的嵌套通信,主要模块包含:

  • 路由配置:src/router/demo.ts定义iFrame路由元数据
  • 父组件:src/layouts/iframe/index.vue承载iFrame容器
  • 通信组件:FramePage.vue处理iFrame加载和消息监听
  • 内嵌页面:@/views/infra/frame/index.vue作为iFrame内容页
  • 工具函数:useFrame封装跨窗口通信逻辑

二、代码核心部分

src/router/demo.ts

{path: 'operate',name: 'FrameOperate',component: IFrame,meta: {frameSrc: './#/frame/frame',title: '可交互(内嵌)',message(type, data, send) {alert('收到消息来自 iframe, \ntype: ' + type + ', \ndata: ' + data);send('message', '我是父窗口, 我收到了你的消息 ' + data);},},},{path: 'frame',name: 'FrameDemo',component: () => import('@/views/infra/frame/index.vue'),meta: {title: 'iFrame',hidden: true,},},

src/layouts/iframe/index.vue

<FramePagev-show="showIframe(url as any)":frameSrc="url.src"@message="frame.route.meta.message"/>

FramePage.vue

<template><div :class="prefixCls"><iframe:src="frameSrc":class="`${prefixCls}--content h-full`"ref="frameRef"@load="hideLoading"></iframe></div>
</template>
<script lang="ts" setup>import { ref } from 'vue';import { useDesign } from '@/hooks/web/useDesign';import { setObj2Url } from '@/utils';import { useTabStore } from '@/store/modules/tab';import { useFrame } from '../useFrame';const { prefixCls } = useDesign('iframe-page');const props = withDefaults(defineProps<{frameSrc: string;}>(),{frameSrc: '',},);
const emits = defineEmits<{(e: 'message', type: string, data: any, send: (type: string, data?: any) => void): void;}>();const loading = ref(true);const frameRef = ref<HTMLIFrameElement>();function hideLoading() {loading.value = false;}// 生成一个唯一标识const iframeId = `iframe-${Date.now()}`;const frameSrc = setObj2Url(props.frameSrc, { __frame__: iframeId });const router = useRouter();const route = useRoute();const tabStore = useTabStore();const { onMessage, sendMessage } = useFrame(iframeId);onMessage((type, data) => {switch (type) {case 'router':router.push(data);break;case 'close':tabStore.closeTabByPath(route.path);break;case 'refresh':tabStore.refreshTab();break;case 'update_name':tabStore.updateTabTitle(data);break;default:emits('message', type, data, (type, data) => {sendMessage(type, data, frameRef.value!.contentWindow!);});break;}});
</script>

useFrame

import { computed, unref } from 'vue';import { RouteLocationRaw, useRouter } from 'vue-router';
import { useEventListener } from '@/hooks/event/useEventListener';export const useFrame = (frameId?: string) => {const router = useRouter();const { currentRoute } = router;const isFrame = computed(() => {const route = unref(currentRoute);const query = route.query;if (query && Reflect.has(query, '__frame__')) {return true && window != window.parent;}return false;});const id = frameId ?? (currentRoute.value.query.__frame__ as string);function sendMessage(type: string, data?: any, target = window.parent) {target?.postMessage({id,type,data,},'*',);}function onMessage(fn: (type: string, data: any) => void) {useEventListener({name: 'message',listener: (event: MessageEvent) => {const { data } = event;if (data.id != id) return;fn(data.type, data.data);},wait: 0,});}

useEventListener

import type { Ref } from 'vue';
import { ref, watch, unref, onUnmounted } from 'vue';
import { useThrottleFn, useDebounceFn } from '@vueuse/core';export type RemoveEventFn = () => void;
export interface UseEventParams {el?: Element | Ref<Element | undefined> | Window | any;name: string;listener: EventListener;options?: boolean | AddEventListenerOptions;debounce?: boolean;wait?: number;
}el = window,name,listener,options,debounce = true,wait = 80,
}: UseEventParams): RemoveEventFn {/* eslint-disable-next-line */let dispose: RemoveEventFn = () => {};const isAddRef = ref(false);
if (el) {const element = ref(el as Element) as Ref<Element>;const handler = debounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait);const realHandler = wait ? handler : listener;const removeEventListener = (e: Element) => {isAddRef.value = true;e.removeEventListener(name, realHandler, options);};const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options);const removeWatch = watch(element,(v, _ov, cleanUp) => {if (v) {!unref(isAddRef) && addEventListener(v);cleanUp(() => removeEventListener(v));}},{ immediate: true },);dispose = () => {removeEventListener(element.value);removeWatch();};onUnmounted(() => dispose());}return dispose;
}

三、逻辑梳理

以sendMessage(‘message’, ‘hello’)发送消息举例
逻辑梳理:

  1. 内嵌的组件执行sendMessage(‘message’, ‘hello’)

  2. 父窗口通过FramPage.vue组件onMessage监听message,

  3. emits(‘message’, type, data, (type, data) => {
    sendMessage(type, data, frameRef.value!.contentWindow!),

  4. @message="frame.route.meta.message"执行父窗口,也就是operate组件的信息
    });,

  5. 执行第一个alert,收到消息来自 iframe

  6. 执行第一个alert的同时,代码中有 send(‘message’, '我是父窗口, 我收到了你的消息 ’ + data);,send对应的之前父窗口定义的(type, data) => {
    sendMessage(type, data, frameRef.value!.contentWindow!);
    });

  7. frameRef.value!.contentWindow对应的是子窗口的信息,所以对子窗口发送信息 —>子窗口接收 onMessage((type, data) => {
    alert('收到消息来自主框架, \ntype: ’ + type + ', \ndata: ’ + data);
    });,

  8. 触发第二个alert执行

四、 核心流程

1. 消息发送流程(子→父)

// 内嵌组件发送消息
sendMessage('message', 'hello')
↓
// useFrame工具发送postMessage
window.parent.postMessage({id: frameId,type: 'message',data: 'hello'
}, '*')
↓
// 父组件FramePage监听message事件
onMessage((type, data) => {emits('message', type, data, sendFn)
})
↓
// 路由元数据执行消息处理
frame.route.meta.message('message', 'hello', send)
alert('收到消息来自iframe')
send('message', '父窗口响应')

2. 消息回传流程(父→子)

// 父窗口发送响应
send('message', '父窗口响应')
↓
// 转换为useFrame的sendMessage调用
sendMessage(type, data, frame.contentWindow)
↓
// 子组件监听message事件
onMessage((type, data) => {alert('收到消息来自主框架')
})

五、设计亮点

1. 模块化通信封装

  • 统一封装postMessage的发送与监
  • routerTo/close/refresh等高阶操作
  • 自动处理窗口标识符(frameId)

2. 双向通信机制

  • 子→父:通过window.parent.postMessage
  • 父→子:使用frame.contentWindow.postMessage
  • 消息路由:父组件通过v-on:message绑定自定义处理器

3. 安全性保障

  • 采用__frame__参数校验窗口标识
  • 限制消息源域(示例中使用’*',生产环境建议指定具体域名)

六 、postMessage与Vue组件的易误解点

1. 浏览器窗口层级 vs Vue组件层级

  • Vue组件层级:指FramePage和内嵌FrameDemo组件的父子关系,是Vue框架内的逻辑结构。
  • 浏览器窗口层级:window.parent指向父级浏览器窗口(即包含iFrame的主页面),window指向当前iFrame的子窗口。与Vue组件的父子关系无关。
// 错误示例:试图通过Vue父子组件直接通信
// 正确方式:必须通过postMessage跨浏览器窗口通信

2. postMessage的target参数陷阱

  • 子窗口→父窗口:target=window.parent
  • 父窗口→子窗口:target=iframe.contentWindow
  • 常见错误:在Vue父组件中误用this.$parent(Vue组件引用),而非window.parent(浏览器窗口引用)。
// 正确实现(父→子)
sendMessage(type, data, frameRef.value.contentWindow); // 直接操作DOM窗口对象// 错误实现(Vue组件引用)
this.$parent.sendMessage(...); // 这会引发错误!

3. sendMessage函数的双重角色

  • 子窗口调用:sendMessage(‘message’, ‘hello’) → 目标是window.parent
  • 父窗口调用:sendMessage(‘reply’, ‘收到’, frame.contentWindow) → 目标是子窗口
  • 注意:函数内部需根据调用场景动态指定target参数。
// 子窗口工具函数(隐式target=window.parent)
function sendMessage(type, data) {window.parent.postMessage({ type, data }, '*');
}// 父窗口工具函数(显式指定子窗口)
function sendMessage(type, data, target=window.parent) {target.postMessage({ type, data }, '*');
}

4. 跨域通信的安全性

  • '‘的危险性:在postMessage中设置’'会允许任意域接收消息,仅用于开发环境。
  • 生产环境最佳实践:
// 白名单验证源域
const allowedOrigins = ['https://父域.com', 'https://子域.com'];
window.addEventListener('message', (e) => {if (!allowedOrigins.includes(e.origin)) return;// 处理消息逻辑
});

5. 与Vue事件系统的结合

  • Vue事件是组件内通信手段:@message="handleMsg"处理同一页面内组件间的消息。
  • postMessage是跨窗口通信手段:需通过DOM操作直接操作window对象。
  • 两者协同:Vue事件可包装postMessage逻辑,但底层仍依赖浏览器API。
// FramePage组件:Vue事件 → postMessage的桥梁
<template><iframe @message="handleVueMessage" />
</template><script>
function handleVueMessage(data) {// 将Vue事件转换为postMessagewindow.parent.postMessage(data, '*');
}
</script>

6. 总结

  • Vue组件层级与浏览器窗口层级是两个独立概念,通信需通过postMessage而非Vue事件。
  • 明确target参数指向的是浏览器窗口,而非Vue组件实例。 严格控制消息源域,避免跨域安全漏洞
http://www.dtcms.com/a/569243.html

相关文章:

  • 做设计有哪些接私活的网站做钓鱼网站获利3万
  • git常用的指令-(工作中常用)
  • <数据集>yolo航拍交通目标识别数据集<目标检测>
  • 做标准件网站在ppt里面做网站链接
  • 关于网站建设的调研报告电商专业就业前景
  • 做百度糯米网站的团队做的比较好的个人网站
  • 天津市建设厅官方网站网站用绝对路径好还是相对路径seo
  • DevExpress WPF v25.2新功能预览 - 支持将JetBrains Rider与报表设计器集成
  • 力扣热题100道之102二叉树的层序遍历
  • SQLite Truncate Table: 完全删除表中的数据
  • 机器学习:数据集的划分
  • 学校网站建设费计入什么科目wordpress拖曳组件
  • 中国城乡和住房建设部网站wordpress微信商城
  • 零基础学JAVA--Day23(final关键字+抽象类及应用模板设计模式)
  • Linux虚拟机配置jupyter环境并在宿主机访问
  • 低空无人机“一网统飞”深度解构:从技术内核到产业落地,重构低空经济操作系统
  • MyBatis 中 resultMap、association、collection标签详解
  • 网站长期建设运营计划书自己怎么健网站视频下载
  • 网站强制qq弹窗代码专业网页设计制作价格
  • QuickRedis
  • 微信小程序开发案例 | 个人相册小程序(上)
  • JAVA多商户家政同城上门服务预约服务抢单派单+自营商城系统支持小程序+APP+公众号+h5
  • ELK 学习笔记
  • 在 Ubuntu 上快速配置 Node.js 环境(附问题说明)
  • discuz修改网站关键词wordpress微信qq登陆
  • 钦州公司做网站网络空间安全专业大学排名
  • ELK 企业级日志分析系统部署与实践
  • AI研究-119 DeepSeek-OCR PyTorch FlashAttn 2.7.3 推理与部署 模型规模与资源详细分析
  • 1.3.课设实验-数据结构-栈、队列-银行叫号系统
  • 网站如何做监测链接做问卷赚钱的网站