iframe與主視窗通訊

2023-07-27 09:01:09

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: