1、
什么是iframe跨域导致的高度自适应问题
网页A中嵌套了iframe页面B,
如果A和B不在同一个域名下,就形成跨域,
两者之间的通信就会受到浏览器的某种限制。
一种十分常见的场景是,
A需要知道B的高度值以确定iframe的高度,
由于跨域的限制,A又不能通过dom直接地读到B的高度。
这就是常见的iframe高度自适应问题。
如何界定是否同域呢?只有域名和端口号都相同时,才算是同域。
比如说
www.a.com:80和
www.a.com:8080
就不是同域。
www.a.com和
www.c.com也不是同域
。
www.a.com和
www.a.com才算同域。
2、
桥文件方案
在B页面中嵌套一个与A同域的iframeC。那么,
上述场景中的高度值的传递可以这样进行:
a)B获取到自身的高度值h
b)B将"#h"附加到C的网址后面(即修改hash值)
c)C从自身的hash值中分离出h
d)C通过parent.parent.document.
getElementById(id)
来修改A中iframe的高度为h
可见,A是通过C获得B的高度,
C就像是在A和B之间搭的一座桥,所以常把C称作桥文件。
桥文件方案是酷讯各站点最常用的跨域方案。
示例:
A页面(
www.a.com/index.html)主要代码:
<iframe src="
www.b.com/index.html" id="iframe"></iframe>
B页面(
www.b.com/index.html)主要代码
<iframe src="
www.a.com/bridge.html" id="iframe_bridge"></iframe>
<div style="height:80px">test<div>
<div style="height:80px">test<div>
C页面(
www.a.com/bridge.html)主要代码
注意:link中的hash值就是iframe的id;
iframe的初始height为0,
而且height的display必须为block(默认值)。
pm_parent.js代码如下,KXLY.showAd()
函数中的数组是由iframeid组成的,
iframeid不存在时会自动忽略。
var iObj = parent.parent.document.getElementById('iframe');
var iObjH = window.location.hash;
var hashValue = iObjH.split("#");
iObj.style.height = (hashValue[1] != 0 ? hashValue[1] : "0") + "px";
另外,还有使用flash/Json等方式实现的跨域,
没有仔细研究过,在此不作介绍。
3、
postMessage+window.name方案
大多数新的浏览器(IE8+/firefox/chrome/
opera等)支持js的postMessage方法,
可以直接在不同域之间通信。ie6/
ie7浏览器则可以使用js轮询自身
window.name的变
化来实现通信。两者结合起来可组成一种跨浏览器的跨域方案。
这在网友三水清的博文中有详细介绍(
http://js8. in/752.html)。博文末尾还提供了一个不错的示例(
h ttp://js8.in/mywork/ crossdomain/xdomain.html)。
这种方案的关键在于
window.name,
其原理可以用下面一张图来简单说明:
图在附件中
同理,父窗口修改了子窗口的
window.name后,
子窗口也能通过轮询获得相应的信息。
容易发现,当一个父窗口中包含多个子窗口,
这些子窗口向父窗口传递消息时,都必须修改父窗口的
window .name,这就存在通道冲突。
所以该方案在处理同一页面多个iframe时不能适用。
这就要看下面要介绍的升级版pm+
window.name了。
4、
postMessage+window.name升级版
桥文件的优点是方便易实现, 缺点是难以应对一个项目中存在多个域名之间跨域的情况。
postMessage+ window.name方案一般情况下 可代替桥文件方案,而且能处理多个域名的情况。
桥文件的优点是方便易实现,
postMessage+ window.name方案一般情况下
如果一个项目中包含多个域名,
而且一个页面中嵌套不同域名的多个iframe时,
上述两种方式都难以处理,
这正是我最近做机票seo页面项目时遇到的情况。
该项目涉及10几个域名,而且有nginx反向代理,
同一个页面中嵌有酒店的iframe,还有GA广告,
一个页面最多出现3个iframe。通过桥文件的方式非常麻烦,
而且不易于项目的维护。
升级版的方案仍然后是利用3中的原理实现的,
只是增加了父窗口对子窗口的流程控制,
使得子窗口的消息是按一个对列来传递的,从而避免了通道冲突。
这种方案支持任意多域名和一个页面中任意数量iframe的情况
。使用这种方案,只需要很简单的两步设置:
第一步、父窗口
www.a.com/index.html加入i
frame和pm_parent.js
<iframe width="728" scrolling="no" src="" link="www.b.com/index.html#ads1" frameborder="0" marginheight="0" marginwidth="0" style="height:0" id="ads1"></iframe>
<iframe width="710" scrolling="no" src="" link="www.c.oom/index.html#ads2" frameborder="0" marginheight="0" marginwidth="0" style="height:0" id="ads2"></iframe>
<iframe width="728" scrolling="no" src="" link="http://www.d.com/index.html#ads3" frameborder="0" marginheight="0" marginwidth="0" style="height:0" id="ads3"></iframe>
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="pm_parent.js"></script>
注意:link中的hash值就是iframe的id;
pm_parent.js代码如下,KXLY.showAd()
(function(win,doc){
var dt = [],
usePM = (typeof win.postMessage !== 'undefined'),
show = function(data){
data = parseFormat(data);
var success=false;
if(data.height && data.ifr_id){
var height = parseInt(data.height,10);
var ifr_id = data.ifr_id;
if(ifr_id == 'hotel_bottom_recommend'){
height += 48;
}
$('#'+ifr_id).css('height',height+'px').show();
success = true;
}
return success;
},
parseFormat = function(responseText) {
var users = [],
usersEncoded = responseText.split(';'),
userArray,key,value;
for (var i = 0, len = usersEncoded.length; i < len; i++) {
userArray = usersEncoded[i].split(':');
key = userArray[0] || '';
value = userArray[1] || '';
if(!key || !value){
continue;
}else{
users[key] = value;
}
}
return users;
},
getIfr = function(id){
if(id == undefined){
return '';
}
var ifr;
try{
var ifr = doc.getElementById(id).contentWindow;
}catch(e){
}
return ifr;
},
setSrc = function(ads){
var adstrue = [];
for(var i=0,len=ads.length;i<len;i++){
var $obj = $('#'+ads[i]);
if($obj.length > 0){
$obj.attr('src',$obj.attr('link'));
adstrue.push(ads[i]);
}
}
return adstrue;
},
api = {
showAd : function(ads){
var len = ads.length;
if(len < 1){
return ;
}
var adstrue = setSrc(ads);
if(usePM){
if (win.addEventListener) {
win.addEventListener("message",function(e){
show(e.data);
},false);
}else if(win.attachEvent) {
win.attachEvent("onmessage",function(e){
show(e.data);
});
}
}else{
var iframeid = adstrue.pop();
var ifr = getIfr(iframeid);
var hash = '',tmp;
var start = 'ready|'+iframeid;
win.name = '';
var interval = setInterval(function(){
tmp = win.name;
if(typeof ifr != 'undefined'){
ifr.name = start;
}else{
clearInterval(interval);
}
if(tmp !== hash){
hash = tmp;
//获得该iframe的高度后给下一个iframe发通知
if(show(hash)){
ifr.name = 'end|'+iframeid;
iframeid = adstrue.pop();
ifr = getIfr(iframeid);
start = 'ready|'+iframeid;
}
}
},50);
}
}
}
win.KXLY = api;
KXLY.showAd(['ads1','ads2','ads3','ads4']);
})(window,document);
第二步、任何一个iframe页面中,只需要引用同一个pm_client.js即可,pm_client.js代码如下:
(function(win){
var VERSION="1.0.1";
var ifr = win.parent,
ifr_id = location.hash.slice(1);
if(!ifr_id){
return ;
}
//将obj转换为普通格式:a:b;c:d
var buildFormat = function(obj){
var result = [];
result.push('ifr_id:'+ifr_id);
for(var p in obj){
result.push(p+':'+obj[p]);
}
result = result.join(';');
return result;
},
getHeight = function(){
var doc = document;
var win = doc.defaultView || doc.parentWindow,
mode = doc.compatMode,
root = (mode == 'CSS1Compat' ? doc.documentElement:doc.body),
h = win.innerHeight || 0,
scrollH = root.scrollHeight;
scrollH = Math.max(scrollH, h);
offsetH = root.offsetHeight;
return (scrollH == h ? offsetH:scrollH);
},
api = {
adjustHeight : function(){
height = getHeight();
var obj = {height:height};
if(win.postMessage){
ifr.postMessage(buildFormat(obj),'*');
}else{
var hash='',tmp;
var start='ready|'+ifr_id;
setInterval(function(){
tmp = win.name;
if(tmp != hash && tmp == start){
hash = tmp;
ifr.name = buildFormat(obj);
}
},50);
}
}
}
window.KXLY = api;
window.KXLY.VERSION = VERSION;
KXLY.adjustHeight();
})(window);
来源: http://www.fenglinblog.com/?p=6