跨網域控制iframe

前言

最近的業務需求,要在DFP上放視差廣告,就是原本比較小的圖片,滑到一定的高度之後,會自己變大。之前的做法是準備兩個iframe,需要的時候才顯示其中一個,但是需求方不想要投放兩個DFP廣告,並要直接控制同一個iframe的內容,因此才開始找尋如何跨網域控制iframe的方法。

困難

在HTML 5之前,對於iframe的控制,只能在於同個網域,這邊 完整的表示了同網域控制iframe的方法:

1
2
3
4
5
6
7
8
// 先抓iframe的ID
var iframe = document.getElementById("myIframe");

//抓你想要在iframe下抓的按鈕
var btn = iframe.contentWindow.document.getElementById('mybutton');

//或者直接呼叫iframe下的function
iframe.contentWindow.yourFunction();

這樣的話是還不錯,甚至在之前的案子中,我還利用它來播放iframe裡面的影片(當然現在已經不行了,參考鏈接),但是他不能用在DFP上。DFP廣告的素材都是自己上傳到DFP的Server,再從那邊的Server讀取,上面的方法就失效了。

方法

後來我查到在HTML 5有新的Api, Cross-document messaging,支援度也相當不錯,ie 11有支持。程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Main Page
iframe.addEventListener('load', adsAction, false);

function adsAction() {
// distance between element with window top edge.
let dist = wrapper.getBoundingClientRect().top
if (dist > -250 && dist <= 100) {
// send msg to iframe to told iframe do smth
preview.contentWindow.postMessage('showComplete', 'https://iframeWebsite.dfp');
}

if (dist > 100 || dist < -600) {
preview.contentWindow.postMessage('showPreview', 'https://iframeWebsite.dfp');
}
}

// iframe
window.addEventListener('message', receiver, false);

function receiver(e) {
// Website filtering
if(e.origin === 'http://mainPageWebsite') {
if (e.data === 'showComplete') {
// do the things you want
} else if(e.data === 'showPreview') {
// do the things you want 2
}
}
}

在網頁中,抓了iframe後,對iframepostMassage,在postMessage中可以傳入三個函數,前面的兩個是你要傳的訊息,以及iframe的網域,第三個是可選的參數,我還沒研究。
在iframe中,通過addEventListener中的message,會讓iframe收到訊息,,並且通過傳送過來的data來控制iframe。但沒有過濾origin的話,會使得所有其他網域的網站,能控制這個iframe。最好的方法就是,在function中加上網址過濾的判斷。

遇到的問題

在一開始執行時,會出現

1
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://iframeWebsite.dfp') does not match the recipient window's origin ('http://mainPageWebsite').

一開始我以為是http和https的問題,後來查了一下,發現是javascipt很可愛的非同步bug,就是一開始網頁就會在節點載好後,開始執行js,但是因為非同步的關係,iframe的內容還沒有生成,自然postMessage過去就找不到相應的js啦,因此我在網頁js下加上了iframe.addEventListener(‘load’, adsAction, false); 確保iframe載好後重載一次,並且也把因為重載,而造成畫面閃一下的問題處理了。

結論

看到又能用的畫面真的很開心,希望之後能想辦法解決,iframe自動播放影片的問題(好像解決了,下次有就會再分享好了)。