iframe与主窗口通信

1. 引言

<iframe> 元素是 HTML 中的一个标签,用于在当前页面中嵌入另一个页面

使用 <iframe> 可以实现以下功能:

  1. 嵌入其他网页:可以将其他网页嵌入到当前页面中,例如显示地图、视频、文档等
  2. 嵌入本地页面:可以将其他页面或组件嵌入到当前页面中,以实现模块化和复用

嵌入的iframe和主网页之间具有一定的独立性,无法像正常的一个网页上下文一样访问内容,如何通信成为一个问题

本文主要记述iframe和主窗口之间的通信方式,包含内置方法和使用Postmate库

2. 概述

iframe标签在JS中定义为HTMLIFrameElement,而HTMLIFrameElement.contentWindow返回当前HTMLIFrameElementWindow对象,可以使用这个Window 对象去访问这个 iframe 的文档和它内部的 DOM

Window对象可以接收消息(onmessage – Web API 接口参考 | MDN (mozilla.org))和发送消息(window.postMessage – Web API 接口参考 | MDN (mozilla.org)

另外,Window对象还存在父对象(window.parent – Web API 接口参考 | MDN (mozilla.org)),可以通过window.parent访问父对象

利用上述的方法与特性,就可以实现iframe和主窗口之间的通讯

思路之一是:将需要对方访问的属性和方法挂载到window对象上,从而主窗口通过document.querySelector('iframe').contentWindow.<xxx>访问iframe,iframe通过window.parent.<xxx>访问主窗口的属性或方法

思路之二是:主窗口向iframe发送信息postMessage(),并监听子对象的信息,iframe监听主窗口信息并向主窗口发送信息

由于安全原因与浏览器限制,非同源URL(同地址同端口)不可以使用第一种方法,第二种方法均适用

3. 内置方法

3.1 初始准备

笔者准备了这样两个网页,分别叫parent.htmlchildren.html

parent.html内容如下:

<!DOCTYPE html><html lang="en"> <head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title>  <style>    html,    body {      width: 100%;      height: 100%;      margin: 0;      padding: 0;    }     #parent {      width: 100%;      height: 100%;      display: flex;      flex-direction: row;    }     iframe {      width: 80%;      height: 100%;    }     #panel {      width: 20%;      height: 100%;      background: white;      display: flex;      flex-direction: column;    }     code {      font-size: large;    }  </style></head> <body>  <div id="parent">    <iframe src="./children.html" frameborder="0"></iframe>    <div id="panel">      <h3>主窗口</h3>        <code></code>    </div>  </div>   <script>   </script></body> </html>

children.html内容如下:

<!DOCTYPE html><html lang="en"> <head>  <meta charset="UTF-8">  <meta http-equiv="X-UA-Compatible" content="IE=edge">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title>  <style>    html,    body {      width: 100%;      height: 100%;      margin: 0;      padding: 0;    }     #container {      width: 100%;      height: 100%;      background: cadetblue;      display: flex;      flex-direction: column;    }     code {      font-size: large;    }  </style></head> <body>  <div id="container">    <h3>子窗口</h3>    <code></code>  </div>  <script>   </script></body> </html>

这两网页的界面如下:

image-20230726200834842

现在,笔者准备在主窗口与iframe之间传递一个信息

3.2 访问属性

将对方访问的属性和方法挂载到window对象上,从而主窗口通过document.querySelector('iframe').contentWindow.<xxx>访问iframe,iframe通过window.parent.<xxx>访问主窗口的属性或方法

示例代码如下:

// parent.html    const parentMessage = {    name: '李四',    age: 20};Reflect.defineProperty(window, 'parentMessage', {    value: parentMessage}); // 访问子窗口的全局变量const iframe = document.querySelector('iframe');setTimeout(() => {    document.querySelector('code').innerHTML = JSON.stringify(iframe.contentWindow.childMessage)}, 1000); // 等待iframe初始化完毕
// children.html const childMessage = {    name: '张三',    age: 18};Reflect.defineProperty(window, 'childMessage', {    value: childMessage}); document.querySelector('code').innerHTML = JSON.stringify(parent.parentMessage)

结果如下:

image-20230727001146773

注意,这种方法只适用与同源URL,非同源URL访问对方的属性会出现类似错误:

Uncaught DOMException: Blocked a frame with origin "http://127.0.0.1:5500" from accessing a cross-origin frame

3.3 消息发送与监听

主窗口向iframe发送信息postMessage(),并监听子对象的信息,iframe监听主窗口信息并向主窗口发送信息

示例代码如下:

// parent.html const parentMessage = {    name: '李四',    age: 20};const iframe = document.querySelector('iframe');iframe.onload = function () {    // 发送消息    iframe.contentWindow.postMessage(parentMessage, '*');};// 接收消息window.addEventListener('message', function (event) {    const code = document.querySelector('code');    code.innerHTML = JSON.stringify(event.data);});
// children.html const childrenMessage = {    name: '张三',    age: 18};// 接收消息window.addEventListener('message', function (event) {	// 发送消息    window.parent.postMessage(childrenMessage, '*');    const code = document.querySelector('code');    code.innerHTML = JSON.stringify(event.data);});

结果和上面是一样的:

image-20230727001146773

这种方法同源URL与非同源URL都适用

4. Postmate

Postmate 是一个 JavaScript 库,用于简化主窗口和 iframe 之间的跨域通,它基于 window.postMessage() 方法,并提供了一种简单的方式来在主窗口和 iframe 之间发送和接收消息

Postmate 的GitHub地址为:dollarshaveclub/postmate: ? A powerful, simple, promise-based postMessage library. (github.com)

Postmate的特性有:

  • 基于 promise 的 API,用于优雅和简单的通信
  • 安全的双向父 <-> 子握手,并带有消息验证
  • 子代暴露一个可检索的模型对象,父代可以访问
  • 子代发出事件,父代可以监听
  • 父代可以调用子代的函数
  • 零依赖性,如果需要的话,可以为 Promise API 提供自己的 polyfill 或抽象
  • 轻量级,大小约为1.6kb(缩小和压缩后)

Postmate 的主要用法就是:

  1. 主窗口建立握手程序,握手成功后可向iframe获取数据,监听事件以及远程调用函数
  2. iframe建立握手模型,可包含数据、函数等,成功握手后可向主窗口触发事件

大致用法就是如此,有个问题:iframe向主窗口发送数据倒是看起来很简单,直接在主窗口里获取即可,但是如何在iframe里获取主窗口的数据呢,如何才能将主窗口的数据发送给iframe呢?

方法之一是:使用iframe建立的握手模型里远程调用函数,将主窗口传给iframe的数据作为函数的参数(这一点感觉很别扭)

示例代码如下:

parent.html:

<script src="https://cdn.jsdelivr.net/npm/postmate@1.5.2/build/postmate.min.js"></script><div id="parent">    <!-- <iframe src="./children.html" frameborder="0"></iframe> -->    <div id="panel">        <h3>主窗口</h3>        <code></code>    </div></div> <script>    const parentMessage = {        name: '李四',        age: 20    };     // 建立握手程序    const handshake = new Postmate({        container: document.querySelector('#parent'), // 注意将原iframe注释掉        url: './children.html'    });     // 握手成功后    handshake.then(child => {         // 监听事件        child.on('some-event', data => console.log(data)); // Logs "Hello, World!"         // 远程调用函数,将主窗口传给iframe的数据作为函数的参数        child.call('changeParentMessage', parentMessage);         // 获取数据        child.get('childMessage').then(data => {            document.querySelector('code').innerHTML = JSON.stringify(data)        });      });</script>

children.html:

<script src="https://cdn.jsdelivr.net/npm/postmate@1.5.2/build/postmate.min.js"></script><div id="container">    <h3>子窗口</h3>    <code></code></div><script>    const childMessage = {        name: '张三',        age: 18    };     const handshake = new Postmate.Model({        // 建立握手模型 Property values may be functions, promises, or regular values        childMessage: childMessage,        parentMessage: {},        changeParentMessage: function (newMessage) {            this.parentMessage = newMessage;            document.querySelector('code').innerHTML = JSON.stringify(this.parentMessage);        }    });     //成功握手后可向主窗口触发事件    handshake.then(parent => {        parent.emit('some-event', 'Hello, World!');    });</script> 

结果如下:

image-20230727020851043

5. 参考资料

[1] <iframe> – HTML(超文本标记语言) | MDN (mozilla.org)

[2] window.parent – Web API 接口参考 | MDN (mozilla.org)

[3] HTMLIFrameElement.contentWindow – Web API 接口参考 | MDN (mozilla.org)

[4] iframe跨域通信(postMessage) – 掘金 (juejin.cn)

[5] window.postMessage – Web API 接口参考 | MDN (mozilla.org)

[6] dollarshaveclub/postmate: ? A powerful, simple, promise-based postMessage library. (github.com)

[7] 零基础学习 Postmate库 – 掘金 (juejin.cn)

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYqzxk3x' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片