跨瀏覽器的iframe onload 事件監聽(轉)

很多時候,我們會需要改變一個iframe的地址(src屬性),或者使用表單(form)的target在指定的iframe進行提交後,在 iframe加載完畢(onload)時立即響應某個操作,以提高WEB應用程序的價值。本文討論了跨瀏覽器的iframe onload事件的監聽方法。

如果你沒時間去閱讀全文,可以看解決方案的內容概要:

1. 同域的頁面嵌套,最好的是讓內嵌的頁面調用父頁面的函數,如 window.parent.callparentFunctoin()。
2. 如果是異域,或者子頁面已存在且無法修改,那麼:在Firefox/Opera/Safari中,可以直接使用iframe onload事件;而在IE中,可以通過定時器測定子頁面的document.readyState,或者使用iframe onreadystatechange事件計算該事件的響應次數。

以上內容基於參考文檔: Q239638 和 Q188763.

如果你對這個話題很感興趣,請一定要繼續閱讀哦。。。

注[1]:爲了使問題更集中,本文所述的<iframe>均直接寫在父頁面中,對使用 document.createElement(“iframe”)或者其它方式建立的iframe不作討論,因爲這樣會使問題在IE下變得更加複雜,但只要使用本文的結論,無論何方式下建立的iframe,問題仍然會得到解決。

一個簡單的包含iframe的父頁面將是如下樣子的:

<!DOCTYPE ...>
<html xmlns="...">
<head>
<meta ... />
<title>Iframe</title>
<body>
<iframe name="iframe1" id="iframe1" width="300" height="50" src="#" ></iframe>
<script type="text/javascript">//codes here</script>
</body>
</html>

開始:

讓我們從一種簡單的情形和解決方法開始:
1.window.parent 對象
1.1 調用父頁面對象

<!–This is an inner page in the iframe–>
<script type=”text/javascript”>
window.onload=function{ window.parent.iframeCall();}
</script>

在網上找到的方法中,最令人開心的一個,莫過於在能子頁面中調用父頁面的對象了:

window.parent.callFunciton()。

不過我想:可能這點全地球人都已經知道了。只是這個方法有一個缺點,那就是子父頁面必須在同域中。

還有一點,就是前端工程師們需對子頁面有修改權;或者,可以請負責此子頁面的同事爲我們添加一段代碼:

<script type=”text/javascript”>
if(window.parent!=window) window.parent.iframeCall();
</script>

把它放到window.onlad中,或者直接放在</body>之前。

注[2]:在對iframe或其它窗口性質的前端編程中,同域名是最完美的先天條件。只要在同一域名中,各個窗口間的對象是共享的,我們完全可以自由發揮,在不同的窗口間來回駕馭。總之,只有想不到,沒有做不到。
1.2 異域

在不同域名的頁面,瀏覽器出於安全考慮,幾乎完全封鎖了頁面間的對象來往,這裏沒有鵲橋,牛郎和織女只能遠遠想望。當然,用iframe嵌套頁面還是可以的,畢竟還可以思念。

面對家族的封鎖,羅密歐還是很想見朱麗葉,他在夜裏架起梯子抓到朱麗葉的窗前與她見面;在異域的頁面嵌套中,子頁面總是可以直接改變父窗口的location以防止被嵌套,但父頁面對這個一點辦法也沒有。

當然,子頁面除了僅僅永恆地擁有父窗口.location的修改權外,也沒有其它了。例如,在IE下,子頁面只能直接修改父頁面的.location爲另一個源:

<script tyle=”text/javascript”>parent.location=”http://anotherPage.com/”;</script>

但無法訪問其它對象,如window.name,document等,連location.href等location的子屬性就無法訪問。當然,在防止嵌套方面,使用top.location會更強大。

但Firefox中,似乎還可以爲top.location添加一些東西,但這是在我不嚴謹的測試中出現過的情況,未經找到相應的權威文檔哦。
2. iframe onload 事件

在Firefox/Opera/Safari中,直接使用frame元素的onload事件即可:
document.getElementById(“iframe1”).onload=function(){
//your codes here.
};
只可惜它在IE下經常無效,因爲在IE下它最多隻能被激活一次,而且無論你有多少個iframe,被激活的也只能是最後一個的。更詳細的描述請看:Q239638 和 Q188763。

原因
這些事件是在IFRAME內的文檔對象模型中激活的,而不是父頁面的。在IFRAME加載完畢的時候,這個事件就被激活了,而且ReadyState已經是“完成”狀態。所以你無法通過這個事件來查檢一個IFRAME是否加載完畢。

爲了得到更好的表現,我們再稍稍研究一個問題:IFRAME遞歸。
3.IFRAME 遞歸

在處理IFRAME時,瀏覽器應該有一個基本規則,那就是防止遞歸,防止頁面無限的自我加載,使客戶端設備崩潰。事實上,文中出現的幾個瀏覽器均做到這點,只是不同的瀏覽器有不同的處理方式。請分別嘗試以下代碼:
<iframe src=”” onload=”finish()” name=”iframe1”></iframe>
<iframe src=”#hashonly” onload=”finish()” name=”iframe2”></iframe>
<iframe src=”?search” onload=”finish()” name=”iframe3”></iframe>
<iframe src=”http://anotherPage.com” onload=”finish()” name=”iframe4”></iframe>
執行的結果是,在父頁面加載時,上面的iframe onload函數在IE/Opera/Safari中均會被激活,Firefox對第二個沒有反應。這主要因爲他們在防止遞歸方面的處理是不同的。
對於#hashonly和?search這樣的URL,瀏覽器會解釋爲頁面本身。但hash和search的不同之處是,改變 search可以組成新的源,而改變hash不會。通常地,瀏覽器一遇到同源的iframe內頁即會停止加載,但Safari卻會加載多一次。
假如把finish()函數寫成如下:
var finsh=function(){alert(”onload from :”+this.src);}
運行時分別彈出的消息彈出框的次數如下:

ifm/brw: IE | Firefox | Opera | Safari
iframe1: 1 | 1 | 1 | 0
iframe2: 1 | 0 | 1 | 1
iframe3: 2 | 1 | 2 | 2
iframe4: 1 | 1 | 1 | 1

再結合頁面所呈現的內容,可得看出這些瀏覽器在處理遞歸問題上的一些細則:

* Firefox 不會在iframe中加載任何東西和激活onload事件(可能是任何事件)
* IE和Opera不會在iframe中加載頁面,但會激活onload事件。
* Safari(windows版本)會在iframe中加載頁面一次且僅僅一次,並會激活onlaod事件且僅激活依附在父頁面上那個iframe的onload事件。

關於本節,如果僅把iframe用於頁面嵌套,那意義不大;如果用於動態加載/呈現內頁,或者用於良好用戶體驗的form target表單提交處理(不是Ajax),並且要求較高的瀏覽器兼容性時,作用纔會顯示出來。根據本節結果,爲了提高兼容性,最好事先把iframe指向一個空頁面——blank.html,因爲它在4種瀏覽器中的表現是一樣的。如果不想事先加載頁面,那就得花多點心思去判斷瀏覽器類型了。
4.代碼實現
4.1.Firefox/Opera/Safari,直接使用iframe onload事件

document.getElementById(“iframe1”).onload=function(){
//your codes here.
};

4.2.在IE下,定時器測document.readyState或者註冊iframe onreadystatechange事件

4.2.1.定時器以及document.readyState

var fm1=window.frames["iframe1"];
var fmState=function(){
var state=null;
if(document.readyState){
try{
state=fm1.document.readyState;
}catch(e){state=null;}
if(state=="complete" || !state){//loading,interactive,complete
//onComplete();
return;
}
window.setTimeout(fmState,10);
}
};
//在改變src或者通過form target提交表單時,執行語句:
if(fmState.TimeoutInt) window.clearTimeout(fmState.timeoutInt);
fmState.timeoutInt = window.setTimeout(fmState,400);

爲什麼要延時400毫秒?因爲javascript對DOM的操作是異步的,我們必須等待腳本對DOM落實執行後纔開始下一步。400秒這個數取決與客戶端的設備和瀏覽器的響應速度,好的設備的響應速度能在10毫秒以內甚至更快,但100毫秒左右可能比較大衆化,400毫秒應該是十分保守的了。總之,較大的毫秒數能適合更多的用戶設備狀況,並能減少了客戶端設備的工作量。

至於document.readyState,指的是iframe內子頁的docuent.readyState,而不是父頁面的。只要允許,即在同域情況下,document.readyState會返回5個狀態:
uninitialized 對象未初始化.
loading 對象正在加載數據.
loaded 對象已加載完數據.
interactive 在這個狀態下,用戶可以參與互動,即使在對象未加載完畢也可以
complete 對象已完成初始化.

爲什麼使用try和 catch?因爲在異域的情況下,當iframe的子頁到達interactive狀態時,父頁面就會失去訪問權,所以最多隻能返回到loaded這一步,因此IE出一個未知錯誤——其實就是沒有權限,所以try和catch,讓這個錯誤沉默下去。

幸好這個方法只針對IE(目前我能使用到的版本:IE6/7),否則麻煩大了:Opera不等頁面加載完就開始交互了,而IE會等頁面加載完畢才進行交互,所以感覺用Opera打開網頁的速度相對比IE快。

4.2.2.onreadystatechange 事件三步曲

var stateID={};
var fmStChange=function(){
if(ifFirstLoad) return;
stateID[this.id]=stateID[this.id] ? stateID[this.id]+1:1;
switch (stateID[this.id]){
case 1:
//state loading
//onComplete(STEP1);
break;
case 2:
//state interactive
//onComplete(STEP2);
break;
case 3:
//state complete
//onComplete(LASTSTEP);
break;
}
if(stateID[this.id]>=3) stateID[this.id]=null;
};
$("iframe1").onreadystatechange=fmStChange;
//if you want to ignore the parent page load
//add the following two line
var ifFirstLoad=true;
$("iframe1").onload=function(){ifFirstLoad=false;}

每當iframe加載頁面,過程內會激活onreadystatechange事件三次,相應的狀態分別是loading,interactive和complete,而最後一次纔是complete,所以我們得計算一下,直到第三次纔算是完成。

注意:這個方案中的stateID在狀態判斷時非常重要,要進行必要的修正和保護,如在每次應用iframe 時,把stateID[iframe_id]復位爲null,或者在第三次響應完成之前,不要對iframe進行新輪頁面加載,或者在新一輪的頁面加載前消除之前的事件並復位。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章