前端的一個困擾:管不到 iframe
前端有一件麻煩事就是 iframe 這件事,比方:頁面裡有個 iframe,主頁要抓 iframe 裡的東西,或是 iframe 想傳值給主頁。
如果主頁跟 iframe 頁同網域,JS 還可以由主頁抓到 iframe 頁,但兩頁不同網域的話就不行。
August 以前有遇過一個需求,有 2 個頁面,都是公司的,但網域不同,在 A 頁面要 iframe B 頁面,還要無違合的放進頁面裡,高度要剛好,不能出現 Scroll Bar,也不能被截掉。
但在不同網域下,是無法去抓 iframe 的值的,不論是高度或是裡面的參數什麼的。
這時,就需要 postMessage 這個好物,它可以讓 2 個不同網域的頁面互相傳值,因此就只要 B 頁面讀取完時,傳 B 頁面本身的高度給 A 頁,讓 A 頁改變 iframe 的 height
就行。
Google、Facebook 也是這樣傳值的
監聽一下 postMessage 事件並 console.log
,會看見 Google、Facebook 也是這樣在傳值。
Google 廣告或是 FB 的讚按鈕,它們 iframe 到其它網站時,網域一定跟 Google、FB 的不同,而 iframe 又無法抓到外面頁面的值,或是外面頁面想傳值給 iframe 頁時怎麼辦?這時就要用 postMessage。
本篇筆記要記錄的就是 A、B 頁面 iframe 的情況下,A 傳給 B,或 B 傳給 A 的問題,該怎麼用 postMessage 來解決。
postMessage 基本使用
postMessage 是傳遞參數的功能,分為 2 部份:傳值、接值。
傳值,就是 A 把參數傳給 B,這是 postMessage。
接值,就是當 B 收到 A 傳來的 message 後要做什麼,這是 addEventListener message。
傳值
傳值就是用 postMessage,code 如下:
目標視窗.postMessage(要傳的參數, 目標的網域);
要傳的參數:就是要傳的參數(有夠直白XD),比方想傳高度,那就是寫高度。也可以寫一個陣列裡面包多個值。
目標的網域:postMessage 可以指定要傳給哪一個網域,在接值時也可以指定專收哪個網域來的 message。本篇主頁用 https://mother.tw
,iframe 頁用 https://iframe.tw
。
目標視窗:有三種情況,要寫的值不一樣,如下
主頁傳值給 iframe
目標視窗就是 iframe:
var iframe = document.getElementById('iframe').contentWindow; iframe.postMessage('message', 'https://iframe.tw');
iframe 傳值給主頁
目標視窗就是 parent:
parent.postMessage('message', 'https://mother.tw');
主頁用 window.open 的方式開了一個視窗
這部份 August 還沒遇過實際的需求,放上 MDN 的範例:
var popup = window.open(...popup details...); popup.postMessage("hello there!", "http://example.org");
接值
監聽 message 事件就行了:
// 接到 message 要做什麼事的 function function receiveMessage(e) { // 來源網址(e.origin)不是指定的網域時 if(e.origin !== 'https://xxxxxxx.tw') { alert('資料來源錯誤'); return false; } // 來源網址是指定的網域時 else { // 拿傳來的參數(e.data) const data = e.data; } } // 監聽 message 事件 window.addEventListener('message', receiveMessage, false);
在傳值那段的 目標的網域 有寫,傳值、接值都可以指定網域,MDN 上是寫可以用萬用「*」代表不限網域,但那就是連外星人都可以傳值到頁面上,所以建議是寫上指定網域。
function 裡面用 e.origin
來確認傳訊息來的網域是不是你要接收的網域。
比方 iframe 要接來自 https://mother.tw
的 message,但實際頁面上可能還有 Google 廣告、FB 讚鈕的 message 在那傳個滿天飛,總不能每次有 message 來就不斷在執行 function,因此才要先寫 if(e.origin !== 'https://xxxxxxx.tw'){} else { // 做某件事 }
來確保來源網域是 mother.tw。
原始碼、Demo
August 寫了一個頁面 Demo,裡面有 2 個範例:
- iframe 頁傳訊息給母頁:iframe 的高度就是 iframe 頁面本身的高度,無縫 iframe
- 母頁傳訊息給 iframe 頁:母頁(mother.tw)傳訊息給子頁(iframe.tw),讓子頁內容變色。
Demo 的頁面在這:
https://letswritetw.github.io/letswrite-postMessage/
Demo 頁的原始碼也放在 GitHub 上了:
https://github.com/letswritetw/letswrite-postMessage