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


實習不久接到一個任務,在網頁中嵌套另一個工程的網頁。本以為這是輕而易舉的一件事情,結果被測試姐姐折騰得夠嗆。多次和我談心說到這個高度固定導致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引入代理頁面,這樣做有幾個弊端:

  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM