也来谈谈"完美"跨域

关于跨域这个话题,很早就答应过要分享,但是因为懒,一直拖着,直到D2上有人谈起了“完美跨域”。“跨域”应该已经算不上什么难题了,只是提起“完美”这两个字,突然觉得有了新意,那就写点什么吧,至少对自己有个交代嘛!跨域方案有很多,接下来一一枚举,会给出demo,demo的宗旨是尽可能简单的让新手明白,各方案中跨域的原理,实际应用请酌情修改。

方案一、剪贴板

原理:IE本身依附于windows平台的特性为我们提供了一种基于iframe,利用内存来“绕行”的方案,在这里我称之为,本地存储原理。

缺点:不支持非IE浏览器,并且影响到用户对剪贴板的操作,用户体验非常不好,特别是在IE7下,受安全等级影响,会弹出提示框。

演示:[ 点此阅览 ]

子页面在子域:demo.ioldfish.cn下,在页面中加入如下代码获取页面高度并存入剪贴板。

<script type="text/javascript">
var ua = navigator.userAgent;
if ((i = ua.indexOf("MSIE")) >= 0)
{
 var iObjH = window.document.documentElement.scrollHeight;
 window.clipboardData.setData('text',String(iObjH));
}
</script>

主页面在主域:www.ioldfish.cn下,在页面中加入如下代码,获取剪贴板的值并赋值为子页面所在的iframe的高度。

<script type="text/javascript">
window.onload = function()
{
 var iObj =document.getElementById('iId');
 iObj.style.height=parseInt(window.clipboardData.getData('text'))+'px';
}
</script>

方案二、document.domain

原理:设置了document.domain,欺骗浏览器

缺点:无法实现不同主域之间的通讯。并且当在一个页面中还包含有其它的iframe时,会产生安全性异常,拒绝访问。

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,子页面在子域:demo.ioldfish.cn下,在两个页面的头部都加入如下代码:

<script type="text/javascript">
 document.domain="ioldfish.cn";
</script>

方案三、通过JS获取hash值实现通讯

原理:hash可以实现跨域传值实现跨域通讯。

缺点:对于父窗口URL参数动态生成的,处理过程比较复杂一些。并且IE之外的浏览器遇到hash的改变会记录历史,影响浏览器的前进后退功能,体验不佳。

演示:[ 点此阅览 ]

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,因为hash是不受跨域限制的,所以可以将本页面的高度顺利的传送给主页面的hash。

<script type="text/javascript">
 var hashH = document.documentElement.scrollHeight;
 var urlA = "http://www.ioldfish.cn/wp-content/demo/domain/hash/a2.html";
 parent.location.href= urlA+"#"+hashH;
</script>

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,首先取得子页面传递过来的hash值,然后将hash值赋到子页面所在的iframe的高度上。

<script type="text/javascript">
window.onload = function()
{
 var iObj = document.getElementById('iId');
 if(location.hash)
 {
  iObj.style.height=location.hash.split("#")[1]+"px";
 }
}
</script>

方案四、传hash值实现通讯改良版

原理:该方案通过“前端代理”的方式,实现hash的传值,体验上比之方案三有了很大的提升。

缺点:对于父窗口URL参数动态生成的,处理过程比较复杂一些。

演示:[ 点此阅览 ]

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,首先在页面里添加一个和主页面同域的iframe[也可动态生成],他的作用就像是个跳板。C页面中不需任何代码,只要确保有个页面就防止404错误就可以了!该方法通过修改iframe的name值同样可以跨域传值,只是比较”猥琐“而已。

<iframe id="iframeC" name="iframeC" src="http://www.ioldfish.cn/wp-content/demo/domain/hashbetter/c.html" frameborder="0" height="0"></iframe>

然后在页面中加上如下代码,利用页面C的URL接收hash值,并负责把hash值传给同域下的主页面。

<script type="text/javascript">
 hashH = document.documentElement.scrollHeight;
 urlC = "http://www.ioldfish.cn/wp-content/demo/domain/hashbetter/c.html";
 document.getElementById("iframeC").src=urlC+"#"+hashH;
</script>

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,获取从C页面中传递过来的hash值。这里应用到一个技巧,就是直接从A页面用frames["iId"].frames["iframeC"].location.hash,可以直接访问并获取C页面的hash值。这样一来,通过代理页面传递hash值,比起方案三,大大提高了用户体验。

<script type="text/javascript">
window.onload = function()
{
 var iObj = document.getElementById('iId');
 iObjH = frames["iId"].frames["iframeC"].location.hash;
 iObj.style.height = iObjH.split("#")[1]+"px";
}
</script>

方案五、JSONP

原理:有点脚本注入的味道

缺点:服务器端程序运行比脚本早,跨域交互时无法捕获前端页面元素的相关数据,比如自适应高度。

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,动态创建一个script,然后指定到子域的动态文件,在动态文件后面可以添加参数,在这里我加了一个回调函数,当请求返回后,会运行这个回调函数。

<script type="text/javascript">
function loadContent()
{
 var scriptDom=document.createElement('script');
 var url = "http://www.lzdaily.com/domain/jsonp/Test.aspx?f=setDivContent'";
 scriptDom.src= url;
 document.body.appendChild(scriptDom);
}
function setDivContent(love)
{
 var fishDiv = document.getElementById("oldFish");
 fishDiv.innerHTML = love;
}
</script>

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,首先先取得主页面传过来的回调函数名,然后生成一段javascript代码,以回调函数带参数的形式返回主页面,这样就完成了跨域之间的通讯。由于服务器端程序执行总是优先于javascript代码,所以基本上没办法获取到子页面中DOM的相关数据,所以小白同学,我可以很负责人的告诉你,想通过这种方法实现跨域自适应高度是不可能的!

<script language="C#" runat="server">
void Page_Load(object sender, EventArgs e)
{
  string f = Request.QueryString["f"];
  Response.Clear();
  Response.ContentType = "application/x-javascript";
  Response.Write(String.Format(@"{0}({1});", f,1122));
  Response.End();
}
</script>

方案六、window.name

原理:window.name本身并不能实现跨域,只是中间做了代理。

缺点:获取异域的前端页面元素值有一定局限性,比如取自适应高度的值。但是此方法在前端页面元素的数据交互上明显比JSONP强。

演示:[ 点此阅览 ]

这个方案,YAHOO的同事已经给出了详细的demo,我就不重复了,演示demo出自YAHOO克军之手。详细的说明可以参看“怿飞的BLOG”,个人觉得方案四比window.name适用面更广一些。

方案七、window.navigator

原理:window.navigator这个对象是在所有的Iframe中均可以访问的对象。应该是IE的一个漏洞!

缺点:不支持IE外的浏览器下的跨域。严格的dtd申明后,该方法失效!

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,首先先申明一个Json用来保存所有页面的高度window.navigator.PagesHeight={“”:0};,然后根据name的属性找到页面的数据window.navigator.getData,最后将页面的数据注册到window.navigator.PagesHeight中。这里还定义了一个函数resetIframeData,在页面加载的时候调用它,完成跨域的数据交互。注释中详细说明了参数的作用。

<script type="text/javascript">
window.navigator.PagesHeight={"":0};
window.navigator.getData=function(pageName) {
 return window.navigator.PagesHeight[pageName];
};
window.navigator.putData=function(pageName,pageHeight)
{
 window.navigator.PagesHeight[pageName]=pageHeight;
};
/*
 *iframeId:页面中iframe的标识id
 *key:子页面自定义的json标识,这里就是子页面定义的"PortalData".
 *defaultData:无法取到值时候调用
 */

function resetIframeData(iframeId,key,defualtData)
{
 var obj=document.getElementById(iframeId);
 if(window.navigator.getData)
 {
  var pageHeight = window.navigator.getData(key);
  if(pageHeight && String(pageHeight).match(/\d+/))
  {
   obj.style.height=pageHeight+'px';
  }
  else
  {
   obj.style.height=defualtData + 'px';
  }
 }
 else
 {
  obj.style.height=defualtData + 'px';
 }
}
</script>

子页面在主域:www.lzdaily.com下,获取到页面高度后,将高度存到主页定义的Json中,Json标识为”PortalData”,这里可以自定义。

<script type="text/javascript">
window.onload = function getPageData()
{
 var pageHeight = document.body.scrollHeight;
 if(window.navigator.putData)
 {
  window.navigator.putData("PortalData",pageHeight);
 }
}
</script>

其实通过css也可以实现跨域,数据获取的实质其实就是动态载入一段CSS,然后解析其中的字段提取数据,这个方法比较“猥琐”,再这里就不多介绍了,当然flash也可以实现跨域,只是还没去实践,实践完了再补充。啥时候能补完呢?恩……

以上这么多方案,有可以“完美跨域”的吗?单一的看,我想没有吧,都有缺陷,但是只要不同情况下使用合适的方法,我想这才是最完美的!原来绕了一圈,我只是再说废话,哎!不论怎么样,还是希望这些废话对还在苦苦追求“完美”的同学们有所启发!

同发老鱼在线

已有17条评论
  • uidesigner 2008-12-17 12:48:09
    沙发,好多选择哦,哪个方案好呢?
    回复
  • 老鱼 2008-12-18 02:37:18
    最适合你的,才是最好的阿,哈哈,看你是什么应用场景了!
    回复
  • 刘镇维 2008-12-23 04:54:55
    从方案4得到启发,如果可以修改页面4的内容的话,做如下修改就可以实时向父页面传递参数了。 function test(){ var str=location.hash; parent.parent.test(str); }
    回复
  • 刘镇维 2008-12-23 04:56:17
    window.onload=test; 刚才漏了一句
    回复
  • 逍遥游 2009-01-19 10:50:22
    我mail: zhouqm27@gmail.com 有问题想请教一下。你说的所有方案,都是在IE下的吧。 如果在其它浏览器下,譬如chrome/Opera/Safari/FireFox 下,frames['xx'].name是不行的,frames['xx'].location.hash也不行,因为locationd对象不存在----它不是标准,只在IE下有。 总结下来,其它浏览器下,必须用iframe的方式,而且子frame必须用 更改parent.location.hash的方法,而且父窗口必须是顶级窗口。这样一来传的所有东西都被显示于地址栏----一览无余了,而且如果父窗口本身位于iframe或者frameSet中,就不能实现跨域交互。当然,fireFox由于自动刷新,导致跨域交互不能实现。 注:JSONP方式即动态标签的方式根本就不是真正意义的跨域交互,本质上类似于把脚本下载到自己服务器,然后引用自己服务器上的这个脚本。也就是说,注入的东西可以直接包含要用的数据,但是里面的脚本不能访问其母域数据了。谁不信可以试试:被注入的脚步是B域的,直接访问B域的xml数据,A域页面注入此脚本。
    回复
  • 逍遥游 2009-01-19 11:03:53
    “flash也可以实现跨域”---这种就不是js了,说跨域没有任何意义了,因为它本身就是一个完整的应用程序了,确切的说是 Flash/ActiveX/Applet 都可以,直接用基本的socket通信就是了,他们都需要在客户端额外安装插件,这样受限的情况就更多了,一般情况下只能在windows下面用。
    回复
  • oldfish 2009-01-19 15:27:02
    回5楼:你可以看一下demo,事实证明其他浏览器下是支持的,不过方案4在IE6下倒是有个小BUG,最近比较忙,稍后我会给出解决方案,至于jsonp他的实现本质,本文也已经说的很清楚,不妨仔细看看! 回6楼:这里所说的跨域并不仅仅指的是js,个人认为使用什么方式看你的实际情况,flash他确实可以实现跨域,甚至在服务器端配置apache代理.哪种实现成本对于你的现状来说是最小的,那就选哪种,没有必要那么教条的去衡量
    回复
  • oldfish 2009-01-19 15:30:55
    补充一下,jsonp实际的应用场景还是非常广的,如果有兴趣可以线下联系我沟通沟通!
    回复
  • cha369 2009-05-11 01:09:35
    在去年12月份设计dedecms关于启用二级域名情况下的评论的时候涉及到了这个问题, 当时利用的就是类似方案4的解决办法, 这个方案也算完美解决, 兼容了各种浏览器, 麻烦的就是要在建立一个中间文件避免返回404
    回复
  • 天堂左我往右 2009-05-27 02:46:51
    jsonp非常好用,缺点是只能get...
    回复
  • Teddy 2009-08-06 21:41:30
    为何我用方案4,document.getElementById("iid")就返回null, 你们测试过IE7么?
    回复
  • Teddy 2009-08-06 22:16:55
    方案4, 你提到的:技巧,就是直接从A页面用frames["iId"].frames["iframeC"].location.hash,可以直接访问并获取C页面的hash值。 frames["frame_GR"].frames["iframeNFMGR"] 值是 undefined 哪位大侠给指点一下为何?
    回复
  • stc 2010-05-26 08:09:20
    google 的搜索广告是怎么实现的自适应高度的 如 http://goods.jc001.cn/list/sk-%CB%C4%B4%A8+%CA%B5%C4%BE%B5%D8%B0%E5.html 分页处和右边栏
    回复
  • simple 2011-06-30 15:29:02
    看了这么多种,找不到一种适合我的,想请教下:B.html是由A.html使用iframe引进去的,B.html中的JS能获取的到A.html的URL吗? 补充一下:A.html不在我的控制范围内!
    回复
我要评论