<iframe> 元素是 HTML 中的一个标签,用于在当前页面中嵌入另一个页面
使用 <iframe>
可以实现以下功能:
嵌入的iframe和主网页之间具有一定的独立性,无法像正常的一个网页上下文一样访问内容,如何通信成为一个问题
本文主要记述iframe和主窗口之间的通信方式,包含内置方法和使用Postmate库
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(同地址同端口)不可以使用第一种方法,第二种方法均适用
笔者准备了这样两个网页,分别叫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之间传递一个信息
将对方访问的属性和方法挂载到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
主窗口向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都适用
Postmate 是一个 JavaScript 库,用于简化主窗口和 iframe 之间的跨域通,它基于 window.postMessage()
方法,并提供了一种简单的方式来在主窗口和 iframe 之间发送和接收消息
Postmate 的GitHub地址为:dollarshaveclub/postmate: 📭 A powerful, simple, promise-based postMessage library. (github.com)
Postmate的特性有:
Postmate 的主要用法就是:
大致用法就是如此,有个问题: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>
结果如下:
[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)