iframe与主窗口通信
1. 引言
<iframe> 元素是 HTML 中的一个标签,用于在当前页面中嵌入另一个页面
使用 <iframe>
可以实现以下功能:
- 嵌入其他网页:可以将其他网页嵌入到当前页面中,例如显示地图、视频、文档等
- 嵌入本地页面:可以将其他页面或组件嵌入到当前页面中,以实现模块化和复用
嵌入的iframe和主网页之间具有一定的独立性,无法像正常的一个网页上下文一样访问内容,如何通信成为一个问题
本文主要记述iframe和主窗口之间的通信方式,包含内置方法和使用Postmate库
2. 概述
iframe
标签在JS中定义为HTMLIFrameElement
,而HTMLIFrameElement.contentWindow
返回当前HTMLIFrameElement的Window对象,可以使用这个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.html
和children.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 的主要用法就是:
- 主窗口建立握手程序,握手成功后可向iframe获取数据,监听事件以及远程调用函数
- 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与主窗口通信的更多相关文章
- PyQt5 笔记(04):主窗口卡死问题
本文基于:windows 7 + python 3.4 知识点: 1. 将 time.sleep 替换为 QTimer 2. 将 time.sleep 放入到 QThread 3. 使用 QThrea ...
- 使用iframe实现上下窗口结构及登录页全窗口展示Demo
iframe.html 首页 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> < ...
- 解决iframe作为子窗口,刷新后iframe页面跳转到其它页面的问题
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/5990262.html 前言: 在开发网站时,尤其是管理后台,我们经常会使用iframe作为内容窗 ...
- 【2016-11-6】【坚持学习】【Day21】【子窗口关闭时,同步关闭它的主窗口(方法二)】
根据上文,在子窗口设置一个委托.然后在子窗口关闭事件,执行委托实例,然后在主窗口增加监听委托的方法.... 想想,本事关闭事件就是一个特殊的委托.那么干嘛还要特意去声明一个新的呢?多此一举. 于是有下 ...
- Qt5 主窗口组成
1. 菜单栏 菜单是一系列命令的列表.为了实现菜单.工具栏按钮.键盘快捷键等命令的一致性,Qt使用动作(Action)来表示这些命令.Qt的菜单就是由一系列的QAction动作对象构成的列表,而菜单栏 ...
- eclipse中的Console控制台视图脱离主窗口解决办法
问题:Console控制台视图由于操作不当,跑出来了,脱离了主窗口 解决:在eclipse主窗口最上面的工具条选项中,找到Window,点击里面的Reset Perspective,即可,这样视图就重 ...
- MFC主窗口架构模型
根据主窗口类型,MFC软件工程可以分为一下几种架构模型: 1.SDI(Simple Document Interface)单文档界面,一个主窗口下只编辑一份文档 2.MDI(Multiple Docu ...
- 控制窗口不能拖拉出主窗口 Dialog And Window
/* 控制窗口不能拖拉出主窗口(覆写) */var easyuiPanelOnMove = function(left, top) { if ($(this).panel('options').reS ...
- Iframe刷新父窗口的几种方式
/*Iframe刷新父窗口的几种方式在iframe的子页面中,使用onload刷新父页面的时候,遇到了一些问题. 1.目前来说,测试成功,并且兼容IE6/7和FF的刷新方式. */ <scrip ...
- ABBYY是怎么自定义主窗口的
启动 ABBYY FineReader OCR文字设别软件时,将打开其主窗口.同时将显示任务窗口,可以启动内置或自定义自动化任务. 主窗口显示目前打开的 ABBYY FineReader 文档. ● ...
随机推荐
- 第7章. 部署到GiteePages
Gitee Pages 是一个免费的静态网页托管服务,您可以使用 Gitee Pages 托管博客.项目官网等静态网页.如果您使用过 Github Pages 那么您会很快上手使用 Gitee 的 P ...
- Spring注解@Conditional相关用法
1.@Conditional注解 @Conditional 是Spring4新提供的注解. 它的作用是按照一定的条件进行判断,满足条件给容器注册bean,否则不注入. 可以作用在方法上,也可以作用在类 ...
- 音视频八股文(5)--SDL音视频渲染实战。会使用就行,不需要深究。
01-SDL子系统 SDL将功能分成下列数个子系统(subsystem): SDL_INIT_TIMER:定时器 SDL_INIT_AUDIO:音频 SDL_INIT_VIDEO:视频 SDL_INI ...
- 2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码。
2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码. 答案2023-03-30: 这个程序的主要功能是将 MP2 音频文件解码为 PCM 格式,并输出到 ...
- 2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles 。 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐
2022-10-09:我们给出了一个(轴对齐的)二维矩形列表 rectangles . 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标 ...
- 2021-01-09:linux中,某一个实时日志通过什么命令查?
福哥答案2020-01-09:[答案来自此链接:](https://www.zhihu.com/question/438536200)1.tailtail -f首先就是 tail -f,tail 命令 ...
- 2021-06-23:给定一个数组arr,代表每个人的能力值。再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛。一局比赛只有两个人,返回最多可以同时有多少场比赛。
2021-06-23:给定一个数组arr,代表每个人的能力值.再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛.一局比赛只有两个人,返回最多可以同时有多少场比赛. 福大大 答案20 ...
- 【GiraKoo】C++中static关键字的作用
C++中static关键字的作用 在程序中良好的使用static,const,private等关键字,对于代码的健壮性有很大的帮助. 本文介绍的就是C++中static关键字的一些常见用法与区别.适合 ...
- 2015年蓝桥杯C/C++大学B组省赛真题(加法变乘法)
题目描述: 我们都知道:1+2+3+ ... + 49 = 1225 现在要求你把其中两个不相邻的加号变成乘号,使得结果为2015 比如: 1+2+3+...+10*11+12+...+27*28+2 ...
- Springboot——参数校验
springboot参数校验注解 在controller层需要对前端传来的参数进行校验 校验简单数据类型 使用springboot自带的validation工具可以从后端对前端传来的数据进行校验 使用 ...