postMessage窗口间通信实现单点登录
- 方案一:iframe + postMessage + localStorage
- 方案二:postMessage + window.open
- 方案三:BroadcastChannel(推荐)
方案一:本地缓存:使用iframe + postMessage + localStorage
例如:网站1将数据发送到网站2
缺点:网站1和网站2的协议
和域名
要相同
网站1(http://localhost:9090
):
function open(src) {
const iframe = document.createElement("iframe")
iframe.style.display = "none"
iframe.src = src
document.body.append(iframe)
iframe.onload = () => {
iframe.contentWindow.postMessage('my value', src)
}
window.addEventListener('message', (event) => {
if(event.origin === src) {
iframe.remove()
}
})
}
open('http://localhost:2101')
// 可以通过任意方式打开网站2
网站2(http://localhost:2101
):
window.addEventListener("message", (event) => {
localStorage.setItem('win', event.data)
event.source?.postMessage("false", { targetOrigin: event.origin })
}, false)
ps: 虽然看到很多文章都说使用上面这种方式可以让localStorage跨域共享,但是实测必须要网站1与网站2的[协议]://[域名]
相同才行,端口可以不同,否则网站1的iframe(地址为网站2)的localStorage与网站2的localStorage是不共享的。所以上面这种方式并不是完全跨域
网站1
网站2
方案二:页面通信:postMessage + window.open
例如:网站1将数据发送到网站2
缺点:虽然可以实现任意两个网站通信,但是网站2只能在网站1通过window.open
的方式打开才能获取消息
网站1(http://localhost:9090
):
function open(src) {
const win = window.open(src, "_blank")
if(!win) {
return
}
// 很多文章这里用的setTimeout,但是setTimeout存在一些问题
// 1. 延迟时间长
// 2. 存在漏收的情况
// 所以这里使用轮询,每0.5s向另一个页面发送一次消息,当接收方接收到消息后终止发送
const interval = setInterval(() => {
win.postMessage("my value", src)
}, 500)
// 监听另一个页面的回传消息,接收到后终止定时器
window.addEventListener('message', (event) => {
if(event.origin === src) {
clearInterval(interval)
}
})
}
open("http://192.168.10.112:2101")
网站2(http://192.168.10.112:2101
):
let isclose = false
window.addEventListener("message", (event) => {
// 只接收一次
if(isclose) {
return
}
isclose = true
// 回传消息,终止发送
event.source?.postMessage("false", { targetOrigin: event.origin })
localStorage.setItem('data', event.data)
}, false)
注意事项:
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow为目的窗口的引用:iframe.contentWindow或window.open()的返回值
例如:向某个iframe页面发送消息需要用这个iframe的窗口引用进行发送,向某个通过window.open()方式打开的页面发送消息需要用window.open()返回的这个窗口引用进行发送
方案三:BroadcastChannel(推荐)
BroadcastChannel 接口代理了一个命名频道,可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>窗口通信1</h1>
<button onclick="send()">SEND</button>
<script>
const broadCast = new BroadcastChannel("my_demo_client")
function send() {
console.log('send');
broadCast.postMessage("client1")
}
broadCast.onmessage = e => {
console.log(e);
}
</script>
</body>
</html>
页面2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>窗口通信2</h1>
<button onclick="send()">SEND</button>
<script>
const broadCast = new BroadcastChannel("my_demo_client")
function send() {
console.log('send');
broadCast.postMessage("client1")
}
broadCast.onmessage = e => {
console.log(e);
}
</script>
</body>
</html>
由于这两个页面都在my_demo_client
频道,并且在同一个浏览器,可以直接通过频道进行双向通信
点击查看兼容性
如有错误,欢迎指正