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

posted @ 2016-03-23 11:37  法码  阅读(2459)  评论(0编辑  收藏  举报