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>

这两网页的界面如下:

现在,笔者准备在主窗口与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)

结果如下:

注意,这种方法只适用与同源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);
});

结果和上面是一样的:

这种方法同源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>

结果如下:

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)

iframe与主窗口通信的更多相关文章

  1. PyQt5 笔记(04):主窗口卡死问题

    本文基于:windows 7 + python 3.4 知识点: 1. 将 time.sleep 替换为 QTimer 2. 将 time.sleep 放入到 QThread 3. 使用 QThrea ...

  2. 使用iframe实现上下窗口结构及登录页全窗口展示Demo

    iframe.html 首页 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> < ...

  3. 解决iframe作为子窗口,刷新后iframe页面跳转到其它页面的问题

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/5990262.html 前言: 在开发网站时,尤其是管理后台,我们经常会使用iframe作为内容窗 ...

  4. 【2016-11-6】【坚持学习】【Day21】【子窗口关闭时,同步关闭它的主窗口(方法二)】

    根据上文,在子窗口设置一个委托.然后在子窗口关闭事件,执行委托实例,然后在主窗口增加监听委托的方法.... 想想,本事关闭事件就是一个特殊的委托.那么干嘛还要特意去声明一个新的呢?多此一举. 于是有下 ...

  5. Qt5 主窗口组成

    1. 菜单栏 菜单是一系列命令的列表.为了实现菜单.工具栏按钮.键盘快捷键等命令的一致性,Qt使用动作(Action)来表示这些命令.Qt的菜单就是由一系列的QAction动作对象构成的列表,而菜单栏 ...

  6. eclipse中的Console控制台视图脱离主窗口解决办法

    问题:Console控制台视图由于操作不当,跑出来了,脱离了主窗口 解决:在eclipse主窗口最上面的工具条选项中,找到Window,点击里面的Reset Perspective,即可,这样视图就重 ...

  7. MFC主窗口架构模型

    根据主窗口类型,MFC软件工程可以分为一下几种架构模型: 1.SDI(Simple Document Interface)单文档界面,一个主窗口下只编辑一份文档 2.MDI(Multiple Docu ...

  8. 控制窗口不能拖拉出主窗口 Dialog And Window

    /* 控制窗口不能拖拉出主窗口(覆写) */var easyuiPanelOnMove = function(left, top) { if ($(this).panel('options').reS ...

  9. Iframe刷新父窗口的几种方式

    /*Iframe刷新父窗口的几种方式在iframe的子页面中,使用onload刷新父页面的时候,遇到了一些问题. 1.目前来说,测试成功,并且兼容IE6/7和FF的刷新方式. */ <scrip ...

  10. ABBYY是怎么自定义主窗口的

    启动 ABBYY FineReader OCR文字设别软件时,将打开其主窗口.同时将显示任务窗口,可以启动内置或自定义自动化任务. 主窗口显示目前打开的 ABBYY FineReader 文档. ● ...

随机推荐

  1. 迁移学习(MEnsA)《MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point Clouds》

    论文信息 论文标题:MEnsA: Mix-up Ensemble Average for Unsupervised Multi Target Domain Adaptation on 3D Point ...

  2. java无效发源版本xx

    这三个地方统一一下 就可以解决了

  3. SpringBoot定义优雅全局统一Restful API 响应框架四

    如果没有看前面几篇文章请先看前面几篇 SpringBoot定义优雅全局统一Restful API 响应框架 SpringBoot定义优雅全局统一Restful API 响应框架二 SpringBoot ...

  4. 2022-11-19:第二高的薪水。表结构和数据的sql语句如下,输出200,因为200是第二大的。请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT

    2022-11-19:第二高的薪水.表结构和数据的sql语句如下,输出200,因为200是第二大的.请问sql语句如何写? DROP TABLE IF EXISTS `employee`; CREAT ...

  5. Node.js出现‘Cannot find module init’ 解决方法

    1. 首先查看当前根目录是否有node_module文件夹,如果有,请删除 2. 输入 npm clean cache 3. 再次输入 node init -y 大功告成

  6. 程序员的 Windows 工具箱「GitHub 热点速览」

    如何精简 Windows 并快速配置开发环境呢?本周特推的 winutil 是一个程序员的 Windows 工具箱,它提供了开发工具的一键安装以及减少系统垃圾的功能,一切为了简洁.高效.同样高效的还有 ...

  7. L2-3 智能护理中心统计

    题目描述: 智能护理中心系统将辖下的护理点分属若干个大区,例如华东区.华北区等:每个大区又分若干个省来进行管理:省又分市,等等.我们将所有这些有管理或护理功能的单位称为"管理结点" ...

  8. GIT使用的记录

    使用的是win7下的git 安装就不赘述了下面的文档说的很详细 Git客户端图文详解如何安装配置GitHub操作流程攻略 http://www.ihref.com/read-16377.html 到下 ...

  9. [abc279 G] At Most 2 Colors

    G - At Most 2 Colors (atcoder.jp) 重点讲解方法三,因为方法三是蒟蒻都能想出来的方法一和方法二都可以借助方法三的思想推出 方法一 这是最简单的设置状态的方法,\(dp[ ...

  10. SignalR+Hangfire 实现后台任务队列和实时通讯

    SignalR+Hangfire 实现后台任务队列和实时通讯 1.简介: SignalR是一个.NET的开源框架,SignalR可使用Web Socket, Server Sent Events 和 ...