本文并不是一篇iframe API文檔講解,因此想了解iframe API的同學(xué)請移步 MDN, 我將在現(xiàn)在瀏覽器的角度與大家取探討iframe, 因此,本文中雖然會(huì)提及一些iframe在舊瀏覽器中的應(yīng)用, 但并不會(huì)去講解。 所以,您對(duì)iframe在舊瀏覽器中的應(yīng)用場景感興趣的話,還請自己搜索相關(guān)資料。 同時(shí), 我也會(huì)從淺入深的來與大家探討iframe中的一些特性、各種現(xiàn)代瀏覽器中的渲染模式、應(yīng)用場景、以及在現(xiàn)代開發(fā)中的影響。
什么是iframe
在HTML中有三種結(jié)構(gòu)特征:樹結(jié)構(gòu)、層次結(jié)構(gòu)、框結(jié)構(gòu)。iframe正是框結(jié)構(gòu)中的一員。每個(gè)iframe中都是一個(gè)獨(dú)立的沙箱,它們擁有自己的window以及DOM。
為什么需要理解它
雖說在日常開發(fā)中,我們應(yīng)盡量少使用iframe,但在一些特殊場景下,我們也是不可避免需要使用iframe。因此,深入理解iframe能夠讓我們更合理的使用它。
渲染與阻塞
前面講到iframe是HTML三種結(jié)構(gòu)中的框結(jié)構(gòu),框結(jié)構(gòu)中還有另外兩個(gè)元素:frameset和frame,但它們都已廢棄,不再推薦使用。
每一個(gè)框結(jié)構(gòu)都有一個(gè)獨(dú)立的HTML文檔,而不包含以上三種框結(jié)構(gòu)中任意一種的網(wǎng)頁就是最簡單的框結(jié)構(gòu)。其示圖如下:

對(duì)應(yīng)的,復(fù)雜的框結(jié)構(gòu)即多個(gè)框結(jié)構(gòu)復(fù)合在一個(gè)頁面中, 其示圖如下:

像上圖中的多框結(jié)構(gòu),非常不適合移動(dòng)端,因?yàn)檫@種結(jié)構(gòu)的頁面多觸控操作非常不友好。 到此,對(duì)于框結(jié)構(gòu)的基礎(chǔ)知識(shí)普及便告一段落了, 下面筆者將分別從 Chrome、Firefox、Safari、IE 11的測試結(jié)果來分析iframe在不同瀏覽器中的渲染模式以及阻塞情況,代碼如下:
我們先定義iframe要引用的頁面,并編寫如下代碼:
const start = Date.now();
const limit = function() {
return Date.now() - start;
}
while(limit() <= 1000 * 5) {}
接下來, 在主頁面中引入它:
<iframe src="./frame-sets.html"></iframe>
代碼很簡單, 就是讓iframe 阻塞至少5秒鐘,接下來分別在 Chrome、Firefox、Safari、IE11 中測試阻塞情況:
| Chrome | Firefox | Safari | IE11 | |
|---|---|---|---|---|
| 阻塞主頁面渲染 | false | true | true | true |
| 阻塞主頁面onload | true | true | true | true |
從結(jié)果來看,阻塞onload并無異議,從來都是如此,但是驚訝的發(fā)現(xiàn)在Chrome中并不會(huì)阻塞主頁面的渲染, 我猜Chrome為iframe創(chuàng)建來一個(gè)單獨(dú)的沙箱進(jìn)程吧。
無阻塞加載iframe
前面講了iframe與阻塞,在不同的瀏覽器中表現(xiàn)大致相同(只有Chrome不會(huì)阻塞主頁面渲染,onload則都會(huì)受到阻塞)。在極大多數(shù)情況下,iframe都會(huì)阻塞主頁面的渲染, 所以我們急需采用一種不阻塞主頁面渲染的加載iframe的方式。那如果才能做到無阻塞加載iframe呢?思路有二:1. 直接使用setTimeout異步加載iframe;2. 在頁面觸發(fā)onload之后加載iframe。話不多說, 直接亮代碼:
// setTimeout 形式
setTimeout(function() {
frame.src = 'other-page-url';
}, 0);
這種方式十分簡潔, 但是需要注意的是, 如果你需要在頁面onload后執(zhí)行某些操作的時(shí)候, 需要在 setTimeout 回調(diào)中去綁定load函數(shù)。
window.addEventListener('load', function() {
iframe.src = 'other-page-url';
});
這種方式也是簡單粗暴,而且沒有setTimeout方式靈活, 沒辦法準(zhǔn)確到iframe加載完后, 在主頁面做一些操作。
iframe與跨域
跨域是我們開發(fā)過程中經(jīng)常遇到的問題,而如何解決跨域的問題, 網(wǎng)絡(luò)上已經(jīng)有非常多可行的方案, 至于最終選擇何種方案去處理, 還得結(jié)合實(shí)際業(yè)務(wù)場景選擇最合適的方案。接下來,我們將縮小解決方案的范圍, 只限定在iframe中去講解幾種跨域方案。
為了模擬跨域, 我們更改本地hosts。 以mac os 為例:
cd //
cd private/etc
vim hosts
添加如下代碼:
127.0.0.1 demo.com
127.0.0.1 cross.demo.com
127.0.0.1 other.com
- 方案一:
document.domain,這是瀏覽器暴露出來的一個(gè)準(zhǔn)只讀屬性(之所以說它是準(zhǔn)只讀屬性,是因?yàn)樗梢栽O(shè)置為當(dāng)前域名的超級(jí)域),利用這個(gè)特性,可以實(shí)現(xiàn)主域名相同子域名不同的網(wǎng)頁實(shí)現(xiàn)通信。代碼如下:
main.html(http://demo.com:15100/main.html)
document.domain = 'demo.com';
window.alertFrameMsg = function(msg) {
alert(msg);
}
const frame = document.querySelector('#myFrame');
frame.src = 'http://cross.demo:15100/frame-sets.html';
frame-sets.html(http://cross.demo:15100/frame-sets.html)
document.domain = 'demo.com';
parent.alertFrameMsg('hello, world!');
如你所見, 只需要將兩者的document.domain設(shè)置為超域, 就可以實(shí)現(xiàn)主頁面與iframe的跨域通信了。而且相互之間的訪問非常自由(可以雙向通信)
- 方案二:
window.postMessage,HTML5提供的API,可以安全的啟用跨域通信。語法非常簡單:targetWindow.postMessage(data, targetOrigin),第一個(gè)參數(shù)是要傳遞的數(shù)據(jù),令人高興的是將要發(fā)送到目標(biāo)window的數(shù)據(jù),會(huì)采用結(jié)構(gòu)克隆化算法序列化, 這意味著我們無需自己序列化,即可安全的傳輸數(shù)據(jù)對(duì)象到目標(biāo)window。如何在目標(biāo)窗口接收到數(shù)據(jù)呢?編寫如下代碼即可:
window.addEventListener('message', function(evt) {
console.log(evt.data);
}, false);
evt.data 即是 postMessage 中傳遞過來的數(shù)據(jù)! 結(jié)合上下文, 綜合起來:
main.html
window.frames['myFrame'].contentWindow.postMessage({name: 'injser', age: 18}, 'http://other-demo.com:15100');
frame-sets.html
window.addEventListener('message', function(evt) {
console.log(evt.data);
}, false);
這兩種跨域方式在iframe中驗(yàn)證都是可用的, 至于以前一些可用的方案,比如:location.hash、cross-fragment等, 在現(xiàn)代瀏覽器匯總基本驗(yàn)證已不可行。 因此并沒有在這里做出講解。
最后, 作為一名正在路上的前端er, 文章中難免有失誤的地方。 如有發(fā)現(xiàn), 歡迎勘誤!