阅读 187

浏览器间的跨窗口通信之postMessage

origin

最近做项目恰巧遇到需要个小功能,简单描述下

从A页面新开歌窗口打开B页面,B页面完成一系列操作后,需要自动关闭B页面,然后A页面完成一个刷新的动作。

我一想这不就是跨窗口通信么,然后网上搜索引擎一顿查找,发现相关的文章却是不多,最秀的是很多都是复制粘贴的,而且都是围绕 iframe 写的,不算事真正意义上的两个窗口交互,于是就有了这么踩坑文章。

解决方法有很多种,本次只聊 postMessage。本文主要是介绍如何解决这个需求,没有其他干货。

postMessage

原本是是作为跨源通信的新特性出现的,只要能获取要发送窗口的window,就可以消息发出,在接收侧可以将一些不安全的消息进行过滤,防止出现安全问题。

看起来似乎也没有啥兼容性问题,就很棒。

image.png

实现

首先说下文章中用到了几个不同的叫法,但都是同一个意思

t1.html === 表示 A 页面 === 最开始打开的页面

t2.html === 表示 B 页面 === window.open 打开后的页面

无用版

看了 MDN 文档,天真的我以为只需要如下代码就可以完成。

// t1.html <script>   window.addEventListener('message', function(event) {     console.log('event :>> ', event);   }) </script> 复制代码

// t2.html <script>   window.postMessage('123', '/') </script> 复制代码

然后发现根本没用(还是看文档不仔细),直到我看到文档的这里

image.png

所以意思是需要其他窗口的 window 对象来调用 postMessage ,这才监听 message 事件才会被触发。

但是这里回想下最初的需求,是需要 A 页面打开一个新窗口 - B页面, B页面之后再发送一个 message 给 A 页面,此时的想法是这个样子。

image.png

第二版

如果这里调用 postMessage 需要用到 otherWindow 的话,就是意味着需要 B 页面也需要获取 A 页面的 window 对象才可以发送消息。

理下逻辑

1、A 页面打开 B页面,A页面可以从window.open返回值,拿到B页面的window

2、A页面发送 message 给B页面

3、B页面接收到A页面发来的 message,并从中获取到A页面的window

4、此时A页面有B页面的window,B页面也有A页面的window,只要双方都监听 message 事件,就可以双向通信了。

于是有了如下代码

// t1.html <h1>这是 T1</h1> <button id="btn1">打开t2</button> <button id="btn2">发送消息到t2</button> <pre id="text"></pre> <script>   var t2   var btn1 = document.getElementById('btn1')   var btn2 = document.getElementById('btn2')   var text = document.getElementById('text')   btn1.addEventListener('click', function() {     t2 = window.open('/t2.html')   })   btn2.addEventListener('click', function() {     console.log('btn2 点击');     // type 是为了区别和别的应用发送的消息,最后一个参数 '/' 表示只在当前域下有效     t2.postMessage({type: 'popring', message: 't1 发送出去的消息'}, '/')   })   window.addEventListener('message', function(event) {     // 过滤非当前域下的消息     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     // 过滤其他非本应用传递过来的消息,例如 chrome 的插件就可能也会发送消息(表示 wappalyzer 就会)     if(event.data?.type !== 'popring') {       return     }     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

// t2.html <h1>这是 T2</h1> <button id="btn">发送消息到 t1</button> <pre id="text"></pre> <script>   var btn = document.querySelector('#btn')   var text = document.getElementById('text')   var t1   btn.addEventListener('click', function() {     t1.postMessage({type: 'popring', message: 't2 发送出去的消息'}, '/')   })   window.addEventListener('message', function(event) {     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     if(event.data?.type !== 'popring') {       return     }     t1 = event.source     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

此时从A页面打开B页面后,需要在A页面点击按钮,发送消息到B页面,这样B页面接收到消息才能获取到A页面的window,略有麻烦,不如把这部做成自动化。

第三版

根据以上遇到的问题,解决办法其实很简单,既然可以获取到B页面的window,那就等重写B页面加载完毕后触发事件就可。那么解决方案就来了,onload、DOMContentLoaded。

经过几次尝试,发现直接A页面使用onload,若B页面也是使用onload事件则会覆盖掉A页面的事件。需要使用 addEventListener 来监听 window 的 load 或者 DOMContentLoaded 可以生效。addEventListener 特性如下。

image.png

<h1>这是 T1</h1> <button id="btn1">打开t2</button> <button id="btn2">发送消息到t2</button> <pre id="text"></pre> <script>   var t2   var btn1 = document.getElementById('btn1')   var btn2 = document.getElementById('btn2')   var text = document.getElementById('text')   btn1.addEventListener('click', function() {     t2 = window.open('/t2.html')     // 若t2页面没有重写 onload 方法,则在t1页面这么写是ok的,但若t2页面已重写 onload 方法,则以下方法不生效。     t2.onload = () => {       btn2.click()     }     // 可以改写为以下写法     t2.window.addEventListener('load', function() {         btn2.click()     })   })   btn2.addEventListener('click', function() {     console.log('btn2 点击');     t2.postMessage({type: 'popring', message: 't1 发送出去的消息'}, '/')   })   window.addEventListener('message', function(event) {     // 过滤非当前域下的消息     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     // 过滤其他非本应用传递过来的消息,例如 chrome 的插件就可能也会发送消息(表示 wappalyzer 就会)     if(event.data?.type !== 'popring') {       return     }     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

<h1>这是 T2</h1> <button id="btn">发送消息到 t1</button> <pre id="text"></pre> <script>   var btn = document.querySelector('#btn')   var text = document.getElementById('text')   var t1      // t2 定义的 onload 事件   window.onload = function() {     console.log('t2 onload');   }         btn.addEventListener('click', function() {     t1.postMessage({type: 'popring', message: 't2 发送出去的消息'}, '/')   })   window.addEventListener('message', function(event) {     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     if(event.data?.type !== 'popring') {       return     }     t1 = event.source     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

本次使用 DOMContentLoaded 效果如下。

image.png

第四版

现在已经可以两窗口之间进行通信了,剩下的就没有什么了,完善下基本功能即可,顺带发现了几个有趣的 API。

关闭窗口

window.close() 复制代码

只需注意这条命令只可以关闭使用js打开的窗口。

聚焦窗口

window.focus() 复制代码

经过测试只可以从A页面聚焦到B页面,B页面无法调用此函数到A页面。

检查窗口是否关闭

window.closed 复制代码

最后完成的原生 html、js代码

<h1>这是 T1</h1> <button id="btn1">打开t2</button> <button id="btn2">发送消息到t2</button> <pre id="text"></pre> <script>   var t2   var btn1 = document.getElementById('btn1')   var btn2 = document.getElementById('btn2')   var text = document.getElementById('text')   btn1.addEventListener('click', function() {     t2 = window.open('/t2.html')     window.focus()     t2.addEventListener('DOMContentLoaded', function() {       t2.console.log('t1 挂载在 t2 的 DOMContentLoaded');       btn2.click()     })   })   btn2.addEventListener('click', function() {     console.log('btn2 点击');     // t2 页面是否已关闭     if(t2.closed) {       return     }     // postMessage 第三个参数设置为 '/' 表示当前域下传递消息     t2.postMessage({type: 'popring', message: 't1 发送出去的消息'}, '/')     t2.focus()   })   window.addEventListener('message', function(event) {     // 过滤非当前域下的消息     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     // 过滤其他非本应用传递过来的消息,例如 chrome 的插件就可能也会发送消息(表示 wappalyzer 就会)     if(event.data?.type !== 'popring') {       return     }     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

<h1>这是 T2</h1> <button id="btn">发送消息到 t1</button> <pre id="text"></pre> <script>   window.addEventListener('DOMContentLoaded', function() {     console.log('t2 DOMContentLoaded');   })   var btn = document.querySelector('#btn')   var text = document.getElementById('text')   var t1   btn.addEventListener('click', function() {     // t1 页面是否已关闭     if(t1.closed) {       return     }     t1.postMessage({type: 'popring', message: 't2 发送出去的消息'}, '/')     t1.focus()   })   window.addEventListener('message', function(event) {     if(event.origin !== 'http://127.0.0.1:5500' || !event.data) {       return     }     if(event.data?.type !== 'popring') {       return     }     t1 = event.source     text.innerText += (JSON.stringify(event.data)+'\n')   }) </script> 复制代码

可以在 codesandbox 体验下 codesandbox.io/s/modest-sh…

建议新窗口打开体验。

image.png

result

最后总结下结果

1、postMessage 发送需要获取接收方的 window 对象

2、从A页面打开B页面,需要等B页面加载完后才可以监听到事件触发,而A页面可以通过B页面的 DOMContentLoaded 事件来感知B页面加载完毕

3、其他相关的一些 API

4、这只是原生js实现,如果结合 Vue、React相关框架更会有不一样的感觉,而且他们有更加精细的生命周期。


作者:江无花
链接:https://juejin.cn/post/7004111974841712647


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐