大家可能会遇到子页面内容较多但iframe高度不够的情况。给iframe设置scrolling="no"的话子页面内容显示不全,不设置又会出现滚动条从而影响美观。当我们点击不同的菜单让iframe加载不同的html文件时,iframe的高度就需要做相应的调整。
主体思路:子页面加载完成后根据具体body的高度给iframe设置一个适合的高度
情况1:各个子页面内容与高度比较固定
<script>
$(function(){
$("#Frame_Content").load(function(){
var frame_content = $(this);
//获取子页面body的高度 并适量增加
var mainheight = frame_content.contents().find("body").height()+30;
//给iframe设置高度(不低于350)
frame_content.height(Math.max(mainheight,350));
});
});
</script>
<iframe class="main" onload="this.height=350" frameborder="0" scrolling="no" id="Frame_Content" name="content"></iframe>
这样每次加载完子页面,就可以根据实际的内容来给iframe设置相应的高度。
情况2:子页面本身内容在不断变化
有的情况下,子页面发起Ajax请求,从后台拿到新的数据后增加或减少子页面的内容,如果只是在iframe load完成后设定好高度依然没办法真正地动态调整。到这里,我想大家肯定第一反应是使用监听事件。如果可以对子页面body的高度进行监听,那么每当内容有所调整的时候触发事件并对iframe的高度进行设置。不过这里有个问题,window对象有onresize事件,但普通的dom元素是没有的,也就是说想监听也没办法监听。想要解决这个问题,两条路摆在我们面前:
1.写一个轮询来不停的获取子页面body的高度,如果发生了变化就给iframe高度赋新的值;
2.利用jquery的事件机制来模拟一个普通元素上的resize事件。(当然只要能实现resize事件,怎么弄都可以,这里只说jquery)
仔细来看,其实1和2的原理是一样的,事件监听本质上其实应该也是轮询。但完整的事件机制肯定是比我们自己去写一个轮询要成熟的,而且适用所有的dom元素,如果自己写个轮询肯定能做的事就很单一,只有针对性的功能。因为我这里的页面只有一个主iframe,外面就一个框架,横向的主菜单和左侧纵向的二级、三级菜单,页面的主要内容都在iframe根据不同菜单加载的子页面里,等于说只要把唯一的iframe高度调整ok就行。这里我们不妨先简单搞一个轮询:
<script>
$(function(){
var timer;
$("#Frame_Content").load(function(){
if (timer){
clearInterval(timer);
}
//pre_height用于记录上次检查时body的高度
//mainheight用于获取本次检查时body的高度,并赋予iframe的高度
var mainheight,pre_height;
var frame_content = $(this);
timer = setInterval(function(){
mainheight = frame_content.contents().find("body").height() + 30;
if (mainheight != pre_height){
pre_height = mainheight;
frame_content.height(Math.max(mainheight,350));
}
},500);//每0.5秒检查一次
});
});
</script>
如果场景比较简单,一个轮询就可以搞定iframe动态调整高度的问题,比起自己写一个jquery的插件来实现resize事件肯定要轻量很多。但这样的做法毕竟是不够完善的,如果有的页面需要对多个dom元素(不局限于iframe)的高度进行监听,搞很多轮询看起来可能很不规范,同时也不便于管理。
文章里提到一个插件已经实现了上述的功能,并贴出了核心代码。代码长度很短也很简单,感兴趣的话可以研究一下,实际上插件也是使用轮询来不断地检查所需要监听的dom元素的高度和宽度,我也在下面贴出来:
(function($, window, undefined) {
var elems = $([]),
jq_resize = $.resize = $.extend($.resize, {}),
timeout_id,
str_setTimeout = 'setTimeout',
str_resize = 'resize',
str_data = str_resize + '-special-event',
str_delay = 'delay',
str_throttle = 'throttleWindow';
jq_resize[str_delay] = 250;
jq_resize[str_throttle] = true;
$.event.special[str_resize] = {
setup: function() {
if (!jq_resize[str_throttle] && this[str_setTimeout]) {
return false;
}
var elem = $(this);
elems = elems.add(elem);
$.data(this, str_data, {
w: elem.width(),
h: elem.height()
});
if (elems.length === 1) {
loopy();
}
},
teardown: function() {
if (!jq_resize[str_throttle] && this[str_setTimeout]) {
return false;
}
var elem = $(this);
elems = elems.not(elem);
elem.removeData(str_data);
if (!elems.length) {
clearTimeout(timeout_id);
}
},
add: function(handleObj) {
if (!jq_resize[str_throttle] && this[str_setTimeout]) {
return false;
}
var old_handler;
function new_handler(e, w, h) {
var elem = $(this),
data = $.data(this, str_data);
data.w = w !== undefined ? w : elem.width();
data.h = h !== undefined ? h : elem.height();
old_handler.apply(this, arguments);
}
if ($.isFunction(handleObj)) {
old_handler = handleObj;
return new_handler;
} else {
old_handler = handleObj.handler;
handleObj.handler = new_handler;
}
}
};
function loopy() {
timeout_id = window[str_setTimeout](function() {
elems.each(function() {
var elem = $(this),
width = elem.width(),
height = elem.height(),
data = $.data(this, str_data);
if (width !== data.w || height !== data.h) {
elem.trigger(str_resize, [data.w = width, data.h = height]);
}
});
loopy();
}, jq_resize[str_delay]);
}
})(jQuery, this);
jquery的事件机制和插件的写法这里就不具体解析了,网上搜一下都可以找到。这方面我也没有深入的学习,不过只要了解一下要点就能看明白上面的插件代码了。
不想了解,也可以直接拿来用:
//bind resize事件
$("body").bind('resize',function(){
//callback
//...
//...
});
ps:
正常情况下对页面中的dom元素高宽进行监听使用上面的插件应该是没什么问题的,但如果有和我一样是对iframe子页面的body进行监听可能会遇到报错。
看过插件的源码后,会发现当我们对n个dom元素bind resize事件时,实际上有一个队列,轮询中会对队列的每一个元素都检查高宽有没有变化,如果高宽改变了就触发resize事件,从而执行我们自己定义的回调函数。unbind后会把相应的元素从队列中踢掉。我这里的场景是,每个菜单项的target都指向同一个iframe,点击不同的菜单项主iframe加载不同的页面。我在iframe完成load的时候给子页面的body bind resize事件。当切换菜单时,新的子页面加入队列,但老的子页面此时已经不存在了,遍历队列去取对应元素的高宽就会出错。对插件的源码进行修改可以解决这个问题,但我个人认为为了这个单一的目的去修改插件的源码还不如最初的时候就用一个简单的轮询搞定。所以不改源码的情况下,建议把bind放到子页面中去做,回调中调用父页面的接口来给iframe高度赋值。