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. 第7章. 部署到GiteePages

    Gitee Pages 是一个免费的静态网页托管服务,您可以使用 Gitee Pages 托管博客.项目官网等静态网页.如果您使用过 Github Pages 那么您会很快上手使用 Gitee 的 P ...

  2. Spring注解@Conditional相关用法

    1.@Conditional注解 @Conditional 是Spring4新提供的注解. 它的作用是按照一定的条件进行判断,满足条件给容器注册bean,否则不注入. 可以作用在方法上,也可以作用在类 ...

  3. 音视频八股文(5)--SDL音视频渲染实战。会使用就行,不需要深究。

    01-SDL子系统 SDL将功能分成下列数个子系统(subsystem): SDL_INIT_TIMER:定时器 SDL_INIT_AUDIO:音频 SDL_INIT_VIDEO:视频 SDL_INI ...

  4. 2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码。

    2023-03-30:用Go语言改写FFmpeg示例decode_audio.c,实现高效音频解码. 答案2023-03-30: 这个程序的主要功能是将 MP2 音频文件解码为 PCM 格式,并输出到 ...

  5. 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 左下角的坐标 ...

  6. 2021-01-09:linux中,某一个实时日志通过什么命令查?

    福哥答案2020-01-09:[答案来自此链接:](https://www.zhihu.com/question/438536200)1.tailtail -f首先就是 tail -f,tail 命令 ...

  7. 2021-06-23:给定一个数组arr,代表每个人的能力值。再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛。一局比赛只有两个人,返回最多可以同时有多少场比赛。

    2021-06-23:给定一个数组arr,代表每个人的能力值.再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛.一局比赛只有两个人,返回最多可以同时有多少场比赛. 福大大 答案20 ...

  8. 【GiraKoo】C++中static关键字的作用

    C++中static关键字的作用 在程序中良好的使用static,const,private等关键字,对于代码的健壮性有很大的帮助. 本文介绍的就是C++中static关键字的一些常见用法与区别.适合 ...

  9. 2015年蓝桥杯C/C++大学B组省赛真题(加法变乘法)

    题目描述: 我们都知道:1+2+3+ ... + 49 = 1225 现在要求你把其中两个不相邻的加号变成乘号,使得结果为2015 比如: 1+2+3+...+10*11+12+...+27*28+2 ...

  10. Springboot——参数校验

    springboot参数校验注解 在controller层需要对前端传来的参数进行校验 校验简单数据类型 使用springboot自带的validation工具可以从后端对前端传来的数据进行校验 使用 ...