iframe的跨域高度自適應(經過跨域頁面中嵌套本域頁面)

實習不久接到一個任務,在網頁中嵌套另外一個工程的網頁。本覺得這是垂手可得的一件事情,結果被測試姐姐折騰得夠嗆。屢次和我談心說到這個高度固定致使iframe出現滾動條有多麼很差看,對於工程總體的影響有多麼惡劣。由於跨域的緣由,這個需求被拖了許久,真是很痛苦的一件事。最終在我離開公司以前搞定了這個單。html

這裏就把個人研究過程寫下來以供你們參考。segmentfault

首先須要瞭解一下何爲同域,何爲跨域:跨域

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操做子頁面。若跨域,則此時瀏覽器會報錯。cookie

子頁面得到父頁面window:app

parent.window

 

midway

可是僅僅如此並不能得到測試姐姐的承認,後來又仔細研究了跨域的高度自適應。dom

其原理並不複雜,在父頁面嵌套子頁面,在子頁面嵌套與父頁面同域的代理頁面,由於父頁面與代理頁面同域,故父頁面與代理頁面可實現通訊。post

子頁面將自身高度定時賦值到代理頁面的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引入代理頁面,這樣作有幾個弊端:

  1. 代碼冗餘。如果直接訪問子頁面,則子頁面的代理頁面便成了冗餘代碼。
  2. 缺少靈活性。如果子頁面還會被其餘其餘頁面所訪問,則再沒法實現自適應功能。

經過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

有空再談。 

 

 

 參考:

  新手學跨域之iframe

  JavaScript跨域總結與解決辦法

  iframe跨域通訊的通用解決方案

  URL的井號

  https://developer.mozilla.org/zh-CN/docs/Web/API/Window/name

  https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hash

相關文章
相關標籤/搜索