0%

iframe跨域通信详解

iframe在项目中经常会使用到,而且通常都存在跨域的情况,那么如何才能安全进行跨源通信呢?最常用的方法就是使用window.postMessage() 方法。

postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

以上摘自MDN 原文 postMessage;

1
2
3
4
5
otherWindow.postMessage(message, targetOrigin, [transfer]);
// otherWindow 其他窗口的引用
// message 将要发送到其他 window 的数据
// targetOrigin 指定目标主机的协议、地址、端口,可以为"*"
// transfer (可选)一般不用

postMessage十分强大,既可以子传父,也可以父传子,并且可以突破同源限制。

接下来我们来实际应用一下,想象一下这样一个场景,在主页面中有一个遮罩层,子页面是一个iframe内嵌的页面,子页面中有一个按钮,现在想要在点击子页面按钮的时候实现遮罩层的切换,该如何实现?这里我们就用到了postMessage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 子页面

<style>
.btn{
width: 100px;
height: 50px;
margin: 60px;
background-color: skyblue;
border-radius: 10px;
text-align: center;
line-height: 50px;
color: azure;
cursor: pointer;
}

.btn:hover {
background-color:cornflowerblue;
}
</style>
<div class="btn">点击按钮</div>

<script>
const btn = document.querySelector('.btn')
btn.addEventListener('click', function(event) {
// 向父页面发送数据
window.parent.postMessage('toggle', '*')
})

// 监听父页面传来的数据
window.addEventListener('message', function(event) {
console.log(event.data.msg)
const span = document.createElement('div')
span.innerText = event.data.msg
document.body.appendChild(span)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 父页面

<style>
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
</style>

<div class="mask">
<iframe frameborder="0" style="width:800px; height: 300px; background-color: white;" src="http://blog.hpf0532.top/test.html" id="authIframe"></iframe>
</div>

<script>
const iframe = document.getElementById('son')
const mask = document.querySelector('.mask')
// 监听子页面传来的数据
window.addEventListener('message', function (e) {
console.log(e.data)
if(e.data === 'toggle') {
mask.classList.toggle('mask')
// 向子页面传输数据
iframe.contentWindow.postMessage({ msg: Date.now() }, 'http://blog.hpf0532.top')
}
}
</script>

上面的代码其实很简单,在子页面里面获取了元素,该元素触发点击事件的时候,向父窗口发送一个消息,传递了一个消息(这个消息参数会在接收页面的e.data查到)。在父页面监听message事件,监听到了就让遮罩层切换。

完成切换后,父窗口也会向子页面发送一个消息。在子页面监听message事件,监听到了就会向子页面的DOM树中添加相应的元素。这样就实现了在跨域的情况下iframe页面之前的数据传递。