iframe頁面相互呼叫方法
本文首發我的簡書
最近的專案中嵌入了外部的iframe,想跨域呼叫自己頁面的方法,點選iframe中的返回按鈕,返回到父級的上一級頁面,因為是自己的專案是單頁應用,所以無法直接使用window.location.href
,這個需求讓我頭疼了兩天(包括衍生出來的問題),解決了這個問題之後我決定總結一下,首先從簡單的開始:
基本概念:
window.self
: 當前視窗自身的引用
window.parent
: 上一級父視窗的引用
window.top
: 最頂層視窗的引用
當頁面中不存在 iframe 巢狀時,則三者均是當前視窗自身的引用。
同域iframe相互呼叫
1. 子頁面呼叫父頁面方法:
window.parent.fatherFn();
2. 父頁面呼叫子頁面方法:
window.sonFrameName.sonFn();
(sonFrameName是iframe的name
值)
下面才是重點(一般嵌入iframe的應用應該都是跨域的吧)
跨域iframe相互呼叫:
首先要了解html5的api——window.postMessage,我實現跨域呼叫都是基於postMessage方法的。
語法:
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
其他視窗的一個引用,比如iframe的contentWindow屬性、執行
window.open
返回的視窗物件、或者是命名過或數值索引的window.frames
message
將要傳送到其他 window的資料。
targetOrigin
通過視窗的origin屬性來指定哪些視窗能接收到訊息事件,其值可以是字串”*”(表示無限制)或者一個URI。 (由於安全原因最好不要使用”*”)
transfer
可選引數(基本用不上,我也沒看懂官方的解釋)
接收訊息:
在window物件上監聽派遣的message,使用window.addEventListener('message',fn)
window.onmessage = fn
,我在專案中使用了後者。無論使用哪種方法都要注意呼叫方法結束後要解綁——window.removeEventListener('message',fn)
,否則很可能會出現重複呼叫的情況。 這裡的fn有一個event引數,拿到的是一個叫做MessageEvent的物件,chrome控制檯輸出是這樣的
MDN上列出了三個重要的引數
data
從其他 window 中傳遞過來的物件。 (即其他頁面傳送過來的訊息)
origin
呼叫 postMessage
時訊息傳送方視窗的 origin. 這個字串由 協議、“://“、域名、“ : 埠號”拼接而成。(即頁面的url)
source
對傳送訊息的視窗物件的引用; 您可以使用此來在具有不同origin的兩個視窗之間建立雙向通訊。(按照MDN上的例子可以在接受message的回撥中將source作為回信的物件,也就是己方頁面)
按照MDN上的要求,為了避免跨站點指令碼攻擊,在接收到訊息的回撥中需要對origin進行判斷,如果不是正確的訊息來源地址,需要return。
下面上自己寫的demo:(x.x.x.x均為本機ip地址)
父頁面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<button id="btn">toSon</button>
<iframe src="http://x.x.x.x:8082/iframe2.html" frameborder="0" name="son"></iframe>
<body>
<script src="http://x.x.x.x:8081/js/jquery.min.js"></script>
<script>
$(function(){
window.addEventListener('message',function(e){
console.log(e);
})
$('#btn').on('click',function(){
window.son.postMessage('fromFather','http://x.x.x.x:8082/iframe2.html');
})
})
</script>
</body>
</html>
子頁面(判斷了訊息來源)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn">toFather</button>
<script src="http://x.x.x.x:8082/js/jquery.min.js"></script>
<script>
$(function(){
$('#btn').on('click',function(){
window.top.postMessage('hi','http://x.x.x.x:8081/iframe1.html');
})
window.addEventListener('message',function(e){
if(e.origin!=='http://x.x.x.x:8081/iframe1.html'){
return;
}
//父頁面點選按鈕,執行子頁面的方法
console.log(e.data);
})
})
</script>
</body>
</html>
我目前的專案使用的是vue,來看看單頁應用中的用法,以及解綁的方法(虛擬碼)
<template>
<div class="full-width full-height drawOnline fix-ios-scroll containHeader">
<MainHeader title="線上繪圖" :backURL="backURL"></MainHeader>
<iframe :src="src"></iframe>
</div>
</template>
<script>
......
export default {
data(){
return{
backURL:'...'
}
},
mounted(){
let _this = this;
//每次生成的例項都不同(this不同),所以使用addEventListener無法解綁
window.onmessage = function (e) {
if(e.data=='backPhoto'){//從iframe頁面中接收到的訊息
_this.iframeBack();
}
}
},
destroyed(){
window.removeEventListener('message',this.iframeBack,false);
},
computed:{
src(){
return '......'
}
},
methods:{
iframeBack(){
this.$router.push(this.backURL);
}
}
......
}
</script>
首先整體流程是在自己的頁面中嵌入了一個外部的iframe,在iframe中點選返回按鈕之後,當前頁面關閉返回上一個頁面。iframe內點選的返回按鈕之後,向當前頁面傳送了’backPhoto’這個訊息,當前頁面接收到這個訊息之後(當時直接採取的判斷訊息名而沒有判斷來源路徑)執行路由跳轉,當前例項包括iframe都會被銷燬,因此在銷燬之前執行了解綁,這樣可以保證每次進入這個頁面window物件重新監聽message,iframeBack這個方法不會重複呼叫。
一開始我是沒注意到解綁這個問題的,測試也沒發現這個問題,直到我進入這個頁面測其它的功能點返回的時候才發現返回的路由地址不對(因為路由末尾有id,而返回的時候只會返回到第一個id的地址),通過除錯發現iframeBack這個方法呼叫了多次,原因在於每進入一次這個頁面window就多監聽了一次message,所以需要在銷燬例項的時候執行解綁。
而怎麼解綁這個問題也是困擾了我半天,一開始無論怎麼解綁都不成功,直到我在某個論壇看到了一個解決方法:使用onmessage
而不是用addEventListener
進行繫結,因為每一次重新生成例項之後iframeBack函式與上一次都不同,所以remove並不能準確的移除上一次繫結的函式。所幸最終還是皆大歡喜完結撒花了。