老生常谈:跨域iframe自适应高度
by jefferkim
at 2011-07-21 22:23:39
original http://yoho88.cn/html/iframe-autoheight.html
不得不承认,这是一个被人说烂了的话题,而我要说的也是最常见的跨域iframe自适应的方法(Iframe代理),今天结合淘宝的做法来讲下这个实现方法。
跨域iframe自适应高度的原理:很简单,假如3个页面,分别是主页面A.html,子页面B.html,代理页面C.html。我们总是希望子页面B.html高度变化时,作为它的外层A.html也跟着变高,不至于产生A.html不能完全展示B.html的内容。让我们看下这三者的关系:
其中A与B是跨域的,而A和C是同域的。它们的关系是:A包含B,B包含C。很显然A和B,以及B和C,因为跨域不能相互通信,而A和C同域,可以相互通信。为此我们就想到让C页面告诉A页面,B页面到底有多少高,这就是C.html作为Proxy.html的概念。因为B和C跨域的是不能相互通信,所以想在C页面中,直接window.parent.document.body.scrollHeight这样的操作是行不通的,一般浏览器会很温柔的告诉你:permission denied to call method 什么什么的。所以我们只能让B页面自己计算自身的高度,然后通过某种方法告诉C页面,再由C页面告诉A页面。
这里的一个方法就是在B页面生成一个Iframe节点,然后设置C页面的src属性,在这个地址上附加一个参数,即B页面计算出来的高度,然后C页面就可以通过window.location获取这个地址栏中的地址,提取出高度值,通过window.top找到A页面,设置A页面的Iframe的高度。
原理讲完了,看下淘宝的实现(不用怀疑,跟你能搜到的基本一样的)
B.html中可以看到这样一句(这时B.html只是指代子页面)
<script data-proxy ="http://i.taobao.com/proxy.htm" src="http://a.xx.cn/loader.js"></script>
先来看那个loader.js,我删了一些跟本文不怎么有关的东西,如下
var Loader = (function() {
var el, _h = 0,
doc = document;
return {
//init传递过来的proxy地址处理
send: function(proxy) {
//加个小延时来处理高度,一般不推荐一直监听所以没用setTimeInterval
setTimeout(function() {
//偷懒,不怎么判断各个浏览器了,直接加个40的调整量了
var sh = doc.body.offsetHeight + 40;
//如果高度有改动,
//则创建新的iframe填充到init函数中创建的div中,
//这样iframe又运行了一次
if (_h != sh) {
_h = sh;
el.innerHTML = '';
}
},
200);
},
init: function(proxy) {
//创建div并填充到document中,
el = doc.createElement("div") el.style.display = "none";
doc.body.appendChild(el);
//读取页面的script,
//并而且scripts[scripts.length - 1]来获取目标script,
//就是包含data-proxy的js,
//然后读取它的src,然后this.send进行先一步处理
var scripts = doc.getElementsByTagName('script');
var _proxyUrl = scripts[scripts.length - 1].getAttribute('data-proxy');
this.send(proxy || _proxyUrl);
}
};
})();
//Yui 2的ready,不用说了,你懂的
YAHOO.util.Event.onDOMReady(function() {
Loader.init();
});
好,再看下script文件中的参数data-proxy,这个参数放置代理页面,其中proxy.html中主要的代码如下: 这段js的作用操作形如下面这种iframe,下面的就是上一步生成的:
(function() {
//分割hash(就是#),它读取到了786,理论上要parseInt下
var height = window.location.hash.substring(1);
try {
//这个el返回的就是B.html的iframe的id为iframeB的元素,反正就是获得B.html
var el = window.top.document.getElementById('iframeB');
if (!el) return;
//把高度赋值给外层iframe B.html,大功告成
el.style.height = height + 'px';
} catch(e) {}
})();
整个过程就是这样的,没用setTimeInterval是因为页面一般也很少有实时更新的,就是用于页面刷新进来就设置外层高度下,再说了setTimeInterval也有其他的一些注意点。
没有实时更新当然也有些麻烦:当你页面真要实时增高减小时就悲催了,所以你一般可以在某个改变页面高度时调用Loader.init(),不幸的是一般你会发生调用错误,第一步你就已经知道了var _proxyUrl = scripts[scripts.length - 1].getAttribute(‘data-proxy’); 获取最后一个script,它希望你将其放到页面底部,也就是说你要调用的js一般都在此文件前面就被引入了。
第一个修改方案是将 var Loader方式改为function Loader,但是这么一来就改动稍微大了些。
第二个方案就是将 var _proxyUrl = scripts[scripts.length - 1].getAttribute(‘data-proxy’);改为
var _proxyUrl = doc.getElementById('iframe_text').getAttribute('data-proxy');
,这样可以先用下。