~~~ L O A D I N G ~~~~~ L O A D I N G ~~~~~ L O A D I N G ~~~

那些被忽略但很好用的 Web API / BroadcastChannel

Sep 30, 2021 Max Lee

里長辦公室廣播:張君雅小妹妹,恁兜欸泡麵已經煮好了!

前兩天已經認識了 PostMessage 和建立專屬頻道的 MessageChannel,它們都是進行點對點的溝通,但如果想要一次跟多個頁面溝通時怎麼辦了,這時 BroadcastChannel 就能派上用場了。


BroadcastChannel

BroadcastChannel 就如同一個無線對講機系統,訊息是播放在一個廣播頻道中,任何頁面只要取得頻道的頻率就可以在無線電中發送/接收訊息。


# 建立頻道

MessageChannel 一樣,BroadcastChannel 本身也是一個 Class,只要透過關鍵字 new 就能建立一個廣播頻道,不過這次我們要傳入一個字串來當作廣播頻道的名稱,未來其他頁面才能藉由同樣的字串來進入頻道。

const channel = new BroadcastChannel("max_channel");

接著只要透過我們建立的廣播頻道送出訊息即可:

const channel = new BroadcastChannel("max_channel");
channel.postMessage("你已成功加入頻道!", location.origin);

要收到訊息的話就用 BroadcastChannel 監聽 message 即可:

const channel = new BroadcastChannel("max_channel");
channel.onmessage = function (event) {
  console.log(event.data);
};

# 頻道溝通

這時候其他頁面只要使用同樣的頻道名稱建立一個 BroadcastChannel,並且一樣透過該頻道來傳送/接收訊息,這樣所有頻道中的頁面就都可以相互溝通了。

<!-- 這裡是 main.html -->
<button onclick="sendMessage()">send message</button>
<iframe src="pageA.html" width="480" height="120"></iframe>
<iframe src="pageB.html" width="480" height="120"></iframe>
<script>
  const channel = new BroadcastChannel("max_channel");
  channel.onmessage = function (event) {
    console.log(event.data);
  };
  function sendMessage() {
    channel.postMessage("你已成功加入頻道!", location.origin);
  }
</script>
<!-- 這裡是 pageA.html -->
<button onclick="sendMessage()">send message</button>
<div class="output">pageA content</div>
<script>
  const output = document.querySelector(".output");
  const channel = new BroadcastChannel("max_channel");
  channel.onmessage = function (event) {
    const output = document.querySelector(".output");
    output.innerHTML = event.data;
  };
  function sendMessage() {
    channel.postMessage("pageA 發送訊息!", location.origin);
  }
</script>
<!-- 這裡是 pageB.html -->
<button onclick="sendMessage()">send message</button>
<div class="output">pageB content</div>
<script>
  const channel = new BroadcastChannel("max_channel");
  channel.onmessage = function (event) {
    const output = document.querySelector(".output");
    output.innerHTML = event.data;
  };
  function sendMessage() {
    channel.postMessage("pageB 發送訊息!", location.origin);
  }
</script>

# 頻道名稱

不過上面這樣的範例,會有一個令人詬病的地方,就是「頻道名稱」的同步問題,要是有其中某個頁面打錯頻道名稱,那就會連不上頻道。或是如果想要更換頻道名稱的時候,就必須大家一起改,似乎是又點不太方便。

所以在主頁面建立頻道後,其實可以先用一般的 postMessage 將頻道名稱傳給需要的頁面:

<iframe src="pageA.html" width="480" height="120"></iframe>
<iframe src="pageB.html" width="480" height="120"></iframe>
<script>
  const allIframe = document.querySelectorAll("iframe");
  const channel = new BroadcastChannel("max_channel");

  // 發送頻道名稱
  allIframe.forEach((iframe) => {
    iframe.addEventListener("load", function () {
      this.contentWindow.postMessage(channel.name, location.origin);
    });
  });

  channel.onmessage = function (event) {
    console.log(event.data);
  };
</script>
<!-- 這裡是 pageA.html -->
<div class="output">pageA content</div>
<script>
  let channel;
  window.addEventListener("message", function (event) {
    if (event.origin !== location.origin) return;
    channel = new BroadcastChannel(event.data);

    channel.onmessage = function (bc_event) {
      const output = document.querySelector(".output");
      output.innerHTML = event.data;
    };

    channel.postMessage("pageA 加入頻道", location.origin);
  });
</script>
<!-- 這裡是 pageB.html -->
<div class="output">pageB content</div>
<script>
  let channel;
  window.addEventListener("message", function (event) {
    if (event.origin !== location.origin) return;
    channel = new BroadcastChannel(event.data);

    channel.onmessage = function (bc_event) {
      const output = document.querySelector(".output");
      output.innerHTML = event.data;
    };

    channel.postMessage("pageB 加入頻道", location.origin);
  });
</script>

補充:如果有頁面想要與廣播頻道斷開連結的話,只要拿建立的頻道執行 close() 即可,關閉後頻道還是存在,只是該頁面不再接收頻道的訊息。


這三天我們已經完全認識了 PostMessage,了解到它不但可以傳送訊息,還可以建立私訊或群組的通訊模式,而這項技術其實時常會用在 Web Worker 中,但因為它的範疇有點龐大,所以這次的系列文章不會介紹到,有興趣的朋友可以再自行研究~


- 此篇文章為「iT 邦幫忙鐵人賽」參賽文章,同步發表於 iT 邦幫忙 -