关键词:iframe,跨域,vue
最近的项目中嵌入了外部的iframe,想跨域调用自己页面的方法,点击iframe中的返回按钮,返回到父级的上一级页面,因为是自己的项目是单页应用,所以无法直接使用window.location.href
,这个需求让我头疼了两天(包括衍生出来的问题),解决了这个问题之后我决定总结一下,首先从简单的开始:
-
基本概念:
window.self
: 当前窗口自身的引用
window.parent
: 上一级父窗口的引用
window.top
: 最顶层窗口的引用
当页面中不存在 iframe 嵌套时,则三者均是当前窗口自身的引用。
-
同域iframe相互调用:
- 子页面调用父页面方法:
window.parent.fatherFn();
- 父页面调用子页面方法:
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) 更新:之前没仔细看,这个origin就是定义上的意思,如https://www.janshu.com
或者http://www.aaa.com:8088
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'){
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并不能准确的移除上一次绑定的函数。所幸最终还是皆大欢喜完结撒花了。
网友评论