實習不久接到一個任務,在網頁中嵌套另一個工程的網頁。本以為這是輕而易舉的一件事情,結果被測試姐姐折騰得夠嗆。多次和我談心說到這個高度固定導致iframe出現滾動條有多么不好看,對於工程整體的影響有多么惡劣。因為跨域的原因,這個需求被拖了許久,真是很痛苦的一件事。最終在我離開公司之前搞定了這個單。
這里就把我的研究過程寫下來以供大家參考。
首先需要了解一下何為同域,何為跨域:
URL | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
同一域名下 | 允許 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js |
同一域名下不同文件夾 | 允許 |
http://www.a.com:8000/a.js http://www.a.com/b.js |
同一域名,不同端口 | 不允許 |
http://www.a.com/a.js https://www.a.com/b.js |
同一域名,不同協議 | 不允許 |
http://www.a.com/a.js http://70.32.92.74/b.js |
域名和域名對應ip | 不允許 |
http://www.a.com/a.js http://script.a.com/b.js |
主域相同,子域不同 | 不允許 |
http://www.a.com/a.js http://a.com/b.js |
同一域名,不同二級域名(同上) | 不允許 |
http://www.cnblogs.com/a.js http://www.a.com/b.js |
不同域名 | 不允許 |
- 此表格來自於 這里。
- 注意:在跨域問題上,域僅僅是通過URL來識別,而不會做出其他額外的解析,舉例來說,127.0.0.1相較於localhost在瀏覽器看來也是跨域了。
- 在同一域名下,不同端口,即使在同一服務器上也算跨域,這就是我所遇到的情況。
contentWindow
在最開始,由於無法完成跨域的高度自適應,我只能放棄跨域的情況,只是做了在同域下的高度自適應,就是表格中第一種和第二種情況。
在同域前提下,以下代碼可獲得iframe頁面的window:
document.getElementById("myframe").contentWindow
獲得子頁面的window后即可在父頁面js操作子頁面。若跨域,則此時瀏覽器會報錯。
子頁面獲得父頁面window:
parent.window
midway
但是僅僅如此並不能獲得測試姐姐的認可,后來又仔細研究了跨域的高度自適應。
其原理並不復雜,在父頁面嵌套子頁面,在子頁面嵌套與父頁面同域的代理頁面,因為父頁面與代理頁面同域,故父頁面與代理頁面可實現通信。
子頁面將自身高度定時賦值到代理頁面的URL后,代理頁面拿到自身URL中的數據傳遞給父頁面,父頁面改變子頁面所在iframe高度。從而達到iframe的高度跨域自適應。
說得比較繞,以下為原理圖:
圖片來自這里。
當然,要做到跨域高度自適應還有一個前提,你必須能操作子頁面,也就是說子頁面也是你能控制的,若是想把baidu.com作為子頁面而達到高度自適應這個就無法實現了。
在子頁面加入以下代碼:
/** * midway 頁面高度自適應代碼 * 在父頁面iframe的src地址后加上midway_url作為線索 * 一可實現功能,二亦是實現動態加載js,實現此功能的通用 * midway_url 為需要嵌入目標頁面實現功能的js地址 */ ;(function() { var str_midway_url = "midway_url"; //從URL中獲取midway_url var midway_reg = new RegExp(str_midway_url + "=([^&]*)(&|$)"); var midway_res = window.location.search.match(midway_reg); //從Cookie中獲取midway_url var cookies = document.cookie; var offset = cookies.indexOf(str_midway_url); //若URL中存在,則獲得並存入Cookie //若Cookie中存在,則獲取 var midway_url = ''; if (midway_res) { midway_url = unescape(midway_res[1]); document.cookie = str_midway_url + "=" + midway_url; } else if (offset != -1) { offset += str_midway_url.length + 1; var end = cookies.indexOf(";", offset); end = (end != -1) ? end : cookies.length; midway_url = cookies.substring(offset, end); } //將目標地址的js引入頁面中,實現功能 if (midway_url) { var dom_script = document.createElement("script"); dom_script.src = midway_url; document.body.appendChild(dom_script); }; })();
父頁面通過iframe標簽引入子頁面, 在子頁面的URL后加上額外的一個參數midway_url。在子頁面的js中加入以上代碼,其核心代碼為一個判斷語句:若url或者cookie中存在此參數,則繼續下一步,若不存在則不再繼續。
一般關於iframe跨域的文章都是在子頁面直接通過iframe引入代理頁面,這樣做有幾個弊端:
- 代碼冗余。若是直接訪問子頁面,則子頁面的代理頁面便成了冗余代碼。
- 缺乏靈活性。若是子頁面還會被其他其他頁面所訪問,則再無法實現自適應功能。
通過URL參數將midway.js傳至子頁面,子頁面以此判斷自己被是被引用的。子頁面創建<script>標簽引入midway.js。
某些頁面因為某些關系需要自我刷新,使得URL中的參數丟失,故將midway_url存入cookie中持久化保存。
被引入子頁面的midway.js為以下內容:
/** * 這是被子頁面引用的js - midway.js * midway.js 必須與 midway.html 在同一目錄 */ ;(function() { var midway = { init: function() { var str_midway_id = "midwayAgentPage"; var midway_url = this.getMidwayUrl(); this.createMidwayIfr(str_midway_id, midway_url); this.setHeight2Ifr(str_midway_id, midway_url); }, //在子頁面中創建代理iframe,其指向與父頁面同域的midway.html createMidwayIfr: function(str_midway_id, midway_url) { var midway_ifr = document.createElement("iframe"); midway_ifr.id = str_midway_id; midway_ifr.src = midway_url; midway_ifr.style.display = "none"; document.body.appendChild(midway_ifr); }, //定時將目標高度值附加到代理Iframe的src地址#之后 setHeight2Ifr: function(str_midway_id, midway_url) { setInterval(function() { //此處獲取目標高度,不宜直接取body高度,應在內容之外套一層div置於body內 var target_height = document.getElementsByTagName("div")[0].scrollHeight; var midway_ifr = document.getElementById(str_midway_id); if (midway_ifr) { midway_ifr.src = midway_url + '#' + target_height; } }, 66); }, //從cookie中得到midway_url return http://.../midway.html getMidwayUrl: function() { var str_midway_url = "midway_url"; var cookies = document.cookie; var offset = cookies.indexOf(str_midway_url); var midway_url = ''; if (offset != -1) { offset += str_midway_url.length + 1; var end = cookies.indexOf(";", offset); end = (end != -1) ? end : cookies.length; midway_url = cookies.substring(offset, end); } if (midway_url) { //原midway_url為...midway.js,此處改為...mindway.html return midway_url.slice(0, midway_url.lastIndexOf("/")) + "/midway.html"; } } }; midway.init(); })();
midway.js的主要功能在於根據midway_url,在子頁面創建iframe引入midway.html,定時將子頁面的實際高度賦值到iframe的src之后。
子頁面中被引入的midway.html的代碼如下:
<!DOCTYPE html> <html> <head> <title>Midway</title> </head> <body> <script> window.onload = function() { //獲得父頁面中的iFrame var ifr = parent.parent.document.getElementById('myframe'); //定時從url中獲取到目標高度 //並賦予目標iFrame相應高度,實現自適應 setInterval(function() { var h = location.hash ? location.hash.substring(1) : 0; if(h) ifr.style.height = h + 'px'; }, 66); }; </script> </body> </html>
midway.html相當於一個中轉站,由於midway.html與父頁面同域可互相通信,故通過midway.html實時改變父頁面中iframe的高度。
父頁面中關於iFrame的代碼如下:
<iframe id="myframe" name="myframe" width=100% height=100% frameborder=0 marginheight=0 marginwidth=0 scrolling="no"> </iframe> <script> ;(function(){ //此處為midway.js的URL //注意:midway.js與midway.html應在同一文件夾 var midway_url = "http://.../midway.js"; //此處將midway.js地址添加到目標頁面的URL后 document.getElementById("myframe").src("http://...&midway_url=" + midway_url); })(); </script>
雖然是比較繞,但實現了測試妹妹需要的功能,也算是完美解決了任務。
document.domain
問題解決的同時,也做了其他的一些研究,也一並寫在下面。
若是在同一主域不同子域的情況下,例如news.a.com與a.com或者news.a.com與blog.a.com之間,可以通過js對於主域的設置來實現相互通信:
document.domain = "a.com"
如此設置便可使用第一種方法實現兩個頁面的自由通信。
window.name
window.name 屬性可設置或返回存放窗口的名稱的一個字符串。利用這個屬性做iframe跨域傳值傳值是很方便的。
具體操作參看這里。
其原理與midway的相似,midway利用一個與父頁面同域的代理頁面,通過改變其location.hash實現跨域傳值,而這里這是通過改變window.name實現跨域傳值,本質上都是通過代理iframe作為中介來實現的。
location.hash能傳遞的數據非常有限。
window.name可以傳遞更多的數據(大小一般為2M,IE和firefox下可以大至32M左右),數據格式可自定義(JSON)。
postMassage 與 window.navigator
有空再談。
參考:
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/name
https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hash