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