一、什么是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;width: 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)
}
