一、什么是iframe?
1. 使用 iframe + postMessage 实现跨域通信
MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
在实际项目开发中可能会碰到在 aa.com 页面中嵌套 bb.com 页面,这时第一反应是使用 iframe,但是产品又提出在 aa.com 中操作,bb.com 中进行显示,或者相反。
postMessage语法:
otherWindow.postMessage(message, targetOrigin, [transfer]); otherWindow:其他窗口的一个引用(在这里我使用了iframe的contentWindow属性) message:将要发送到其他window的数据 targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项
不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,
必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。
不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。 transfer:可选参数
二、遇到的问题
1. postMessage发送消息跨域问题
// 不限制域名就用*,否则就是具体域名,这样可以解决跨域问题 iframe.postMessage(dict, '*')
2. postMessage传递数据的格式
data: {// 最外面这个是postMeaage自带的,下面才是自己定义的数据格式,也可以不要内层的data: data: { responseCode: '000000' body: { id: "" name: "模板1" } } type: "TYPE" }
三、实例代码如下:下面的是iframe实用的例子,应用的是postMessage发送的消息,本例是父组件往子组件传递数据
注意:如果使用postMessage发送消息时,如果不使用按钮触发的话,有可能发送失败,所以下面例子针对此情景做了发送消息失败的处理方案
<template> <div class="main-info"> <iframe ref="iframe" id="iframe" frameborder="0" :src="iframeSrc" style="min-height: 800px; 100%" > </iframe> </div> </template> // 定义数据 data () { return { iframeSrc: '', iframe: '', isReceiveMsg: false, // 是否收到消息,收到消息停止计时器,不再发送postMessage消息 actionNum: 5, // 最多执行5次 timer: null,// 定时器 } }, created() { this.iframeSrc = `http://www.baidu.com` // 监听收到消息 window.addEventListener('message', this.handleMessageEvent) }, mounted () { const self = this this.$nextTick(() => { const iframe = document.getElementById('iframe') if (iframe.attachEvent) { // 适配IE iframe.attachEvent('onload', function () { self.clickIframe() setTimeout(() => { self.handlePostMessageFail() }, 1000) }) } else { iframe.onload = function () { // 坑一,postMessage发送通知时,可能对方的页面还没有加载完成导致发送失败 self.clickIframe() setTimeout(() => { self.handlePostMessageFail() }, 1000) } } }) } }, methods: { handleMessageEvent(event) { if (event.data && event.data.data) { const data = event.data.data const body = data.body || '' if (parseInt(data.responseCode) === 0) { // 成功返回 setTimeout(() => { this.$router.push({ name: this.backPath }) }, 500) } else if (parseInt(data.responseCode) === 2) { // 收到消息 console.log('-------已收到消息', data) this.isReceiveMsg = true } } }, clickIframe() { const iframe = document.getElementById('iframe') && document.getElementById('iframe').contentWindow if (!iframe) return const list = [] list.push(this.processData) const dict = { processList: list } // 不限制域名就用*,否则就是具体域名 iframe.postMessage(dict, '*') }, // 其中clickIframe里是处理iframe的src的 // 处理失败机制 // postMessage消息发送失败机制,上面定义执行5次,第隔1.5秒,之前设置3次,间隔一秒,还是有失败的,所以这里采用这个 handlePostMessageFail () { this.timer = setInterval(() => { if (!this.isReceiveMsg) { if (this.actionNum <= 0) { clearInterval(this.timer) this.timer = null this.isReceiveMsg = true return } this.clickIframe() this.actionNum-- } else { clearInterval(this.timer) this.timer = null this.isReceiveMsg = true } }, 1500) }, // 记得离开页面时,要消毁掉 destroyed() { window.removeEventListener('message', this.handleMessageEvent) }