<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: