本文并不是一篇iframe API文檔講解坎匿,因此想了解iframe API的同學請移步 MDN, 我將在現(xiàn)在瀏覽器的角度與大家取探討iframe, 因此筷登,本文中雖然會提及一些iframe在舊瀏覽器中的應用知市, 但并不會去講解。 所以司恳,您對iframe在舊瀏覽器中的應用場景感興趣的話,還請自己搜索相關資料绍傲。 同時扔傅, 我也會從淺入深的來與大家探討iframe中的一些特性、各種現(xiàn)代瀏覽器中的渲染模式烫饼、應用場景猎塞、以及在現(xiàn)代開發(fā)中的影響。
什么是iframe
在HTML中有三種結(jié)構特征:樹結(jié)構枫弟、層次結(jié)構邢享、框結(jié)構。iframe正是框結(jié)構中的一員淡诗。每個iframe中都是一個獨立的沙箱骇塘,它們擁有自己的window以及DOM。
為什么需要理解它
雖說在日常開發(fā)中韩容,我們應盡量少使用iframe款违,但在一些特殊場景下,我們也是不可避免需要使用iframe群凶。因此插爹,深入理解iframe能夠讓我們更合理的使用它。
渲染與阻塞
前面講到iframe是HTML三種結(jié)構中的框結(jié)構,框結(jié)構中還有另外兩個元素:frameset
和frame
赠尾,但它們都已廢棄力穗,不再推薦使用。
每一個框結(jié)構都有一個獨立的HTML文檔气嫁,而不包含以上三種框結(jié)構中任意一種的網(wǎng)頁就是最簡單的框結(jié)構当窗。其示圖如下:
對應的,復雜的框結(jié)構即多個框結(jié)構復合在一個頁面中寸宵, 其示圖如下:
像上圖中的多框結(jié)構崖面,非常不適合移動端,因為這種結(jié)構的頁面多觸控操作非常不友好梯影。 到此巫员,對于框結(jié)構的基礎知識普及便告一段落了, 下面筆者將分別從 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中并不會阻塞主頁面的渲染聋迎, 我猜Chrome為iframe創(chuàng)建來一個單獨的沙箱進程吧脂矫。
無阻塞加載iframe
前面講了iframe與阻塞,在不同的瀏覽器中表現(xiàn)大致相同(只有Chrome不會阻塞主頁面渲染霉晕,onload則都會受到阻塞)庭再。在極大多數(shù)情況下,iframe都會阻塞主頁面的渲染牺堰, 所以我們急需采用一種不阻塞主頁面渲染的加載iframe的方式拄轻。那如果才能做到無阻塞加載iframe呢?思路有二:1. 直接使用setTimeout
異步加載iframe伟葫;2. 在頁面觸發(fā)onload之后加載iframe恨搓。話不多說, 直接亮代碼:
// setTimeout 形式
setTimeout(function() {
frame.src = 'other-page-url';
}, 0);
這種方式十分簡潔, 但是需要注意的是斧抱, 如果你需要在頁面onload
后執(zhí)行某些操作的時候常拓, 需要在 setTimeout 回調(diào)中去綁定load
函數(shù)。
window.addEventListener('load', function() {
iframe.src = 'other-page-url';
});
這種方式也是簡單粗暴辉浦,而且沒有setTimeout
方式靈活墩邀, 沒辦法準確到iframe加載完后, 在主頁面做一些操作盏浙。
iframe與跨域
跨域是我們開發(fā)過程中經(jīng)常遇到的問題眉睹,而如何解決跨域的問題, 網(wǎng)絡上已經(jīng)有非常多可行的方案废膘, 至于最終選擇何種方案去處理竹海, 還得結(jié)合實際業(yè)務場景選擇最合適的方案。接下來丐黄,我們將縮小解決方案的范圍斋配, 只限定在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
,這是瀏覽器暴露出來的一個準只讀屬性(之所以說它是準只讀屬性桂对,是因為它可以設置為當前域名的超級域)甩卓,利用這個特性,可以實現(xiàn)主域名相同子域名不同的網(wǎng)頁實現(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
設置為超域, 就可以實現(xiàn)主頁面與iframe的跨域通信了宅此。而且相互之間的訪問非常自由(可以雙向通信)
- 方案二:
window.postMessage
机错,HTML5提供的API,可以安全的啟用跨域通信父腕。語法非常簡單:targetWindow.postMessage(data, targetOrigin)
弱匪,第一個參數(shù)是要傳遞的數(shù)據(jù),令人高興的是將要發(fā)送到目標window的數(shù)據(jù)璧亮,會采用結(jié)構克隆化算法序列化萧诫, 這意味著我們無需自己序列化,即可安全的傳輸數(shù)據(jù)對象到目標window杜顺。如何在目標窗口接收到數(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中驗證都是可用的尖奔, 至于以前一些可用的方案,比如:location.hash、cross-fragment等提茁, 在現(xiàn)代瀏覽器匯總基本驗證已不可行淹禾。 因此并沒有在這里做出講解。
最后茴扁, 作為一名正在路上的前端er, 文章中難免有失誤的地方铃岔。 如有發(fā)現(xiàn), 歡迎勘誤峭火!