Javascript公共脚本库系列(四) 改进的弹出层脚本
一.摘要
本篇文章并没有为系列文中构造的轻量级脚本库添加新的方法, 而是改进了原有弹出浮动层的方法. 对方法中获取位置的函数重构出来, 为弹出层自动添加iframe遮盖层以实现IE6下遮住<Select>控件. 又放在此系列文章中也是对自己学习过程的一次记录.
二.关于弹出层无法遮盖select的问题
在IE6下存在一个Bug: 如果弹出层是一个div, 并且在弹出层下方有一个<select>下拉框控件, 则div无论z-index值如何设置都无法遮盖select控件.如图:
目前有两种解决办法:
1.当弹出层出现时隐藏select控件.
个人认为此方法的效果欠佳, 并且没有通用性.
2.在弹出层下添加一个一个iframe
因为在IE6中认为select控件认为是窗口级元素. 所以可以使用同样是窗口级元素的iframe来遮盖住Select控件, 注意需要将iframe的zIndex值设置为大于select控件.
关于添加iframe的方式有很多种.比如在弹出层里面添加一个宽度为div宽+div边框宽的iframe(为了遮盖弹出层边框), 或者在页面上添加一个透明的iframe并控制firame与弹出层的同步显示等.
三.改进版ScriptHelper实现方法
因为我们构建的是通用的轻量级脚本库, 所以我不希望为了使用脚本库还需要在页面上添加特别的iframe元素.而且也不希望在所有的弹出层上都添加一个iframe元素, 因为会增加弹出层的代码.于是通过改进弹出图层showDivCommon和关闭图层closeDivCommon这两个方法实现了动态添加iframe和隐藏iframe.
实现思路:
1.为每个弹出层动态的在Body元素上添加一个div, div中包含一个iframe元素. 当弹出层显示时设置iframe的位置和长宽与弹出层相同, zIndex值为弹出层-1, 关闭时弹出层时也隐藏iframe.
2.iframe和iframe的容器div在第一次弹出时创建, 以后再弹出和关闭不会重新创建.
3.每一个弹出层都会有一个对应的iframe, 以满足一个页面同时弹出多个弹出层的需求
实现代码:
修改后的showDivCommon和closeDivCommon方法:
// 显示图层,再次调用则隐藏 /* 参数说明: sObj : 要弹出图层的事件源 divId : 要显示的图层ID moveTop : 手工向下移动的偏移量.不移动则为0(默认). moveLeft : 手工向左移动的距离.不移动则为0(默认). 用法与测试: <div><a href="#" onclick="ScriptHelperV2.showDivCommon(this,'testDiv', 20, 20)">事件源</a></div> */ scriptHelperV2.prototype.showDivCommon = function(sObj, divId, moveTop, moveLeft) { //取消冒泡事件 if (typeof (window) != 'undefined' && window != null && window.event != null) { window.event.cancelBubble = true; } else if (ScriptHelperV2.showDivCommon.caller.arguments[0] != null) { ScriptHelperV2.showDivCommon.caller.arguments[0].cancelBubble = true; } //参数检测.如果没有传入参数则设置默认值 if (moveLeft == null) { moveLeft = 0; } if (moveTop == null) { moveTop = 0; } var divObj = document.getElementById(divId); //获得弹出图层对象 var sObjOffsetTop = 0; //事件源的垂直距离 var sObjOffsetLeft = 0; //事件源的水平距离 var position = this.getPosition(sObj); //获取事件源对象的偏移量 var myClient = this.getClient(); //获取屏幕大小 var myScroll = this.getScroll(); //获取滚动条滚动的举例 var sWidth = sObj.offsetWidth != null ? parseInt(sObj.offsetWidth) : 0; //事件源对象的宽度 var sHeight = sObj.offsetHeight != null ? parseInt(sObj.offsetHeight) : 20; //事件源对象的高度 var popDivWidth = 0; //弹出层的宽度 var popDivHeight = 0; //弹出层的高度 var bottomSpace; //距离底部的距离 var iframeDivId = "tempIframeDiv" + divId; //iframe所在div的id var iframeId = "tempIframe" + divId; //iframe的id var iframeDiv = document.getElementById(iframeDivId); //iframe所在div对象 var iframe = document.getElementById(iframeId); //iframe对象 if (divObj.style.display.toLowerCase() != "none") { //隐藏图层 divObj.style.display = "none"; //隐藏iframe if (iframe != null) { iframe.style.display = "none"; } if (iframeDiv != null) { iframeDiv.style.display = "none"; } } else { if (sObj == null) { alert("事件源对象为null"); return false; } //先显示图层,才能获取到弹出层的长宽 divObj.style.display = "block"; popDivWidth = divObj.offsetWidth != null ? parseInt(sObj.offsetWidth) : 0; //弹出层宽度 popDivHeight = divObj.offsetHeight != null ? parseInt(divObj.offsetHeight) : 0; //弹出层高度 /* 获取距离底部的距离 */ bottomSpace = parseInt(myClient.clientHeight) - (parseInt(position.OffsetTop) - parseInt(myScroll.scrollTop)) - parseInt(sHeight); /* 设置图层显示位置 */ //如果事件源下方空间不足且上方控件足够容纳弹出层,则在上方显示.否则在下方显示 if (popDivHeight > 0 && bottomSpace < popDivHeight && position.OffsetTop > popDivHeight) { divObj.style.top = (parseInt(position.OffsetTop) - parseInt(popDivHeight)).toString() + "px"; } else { divObj.style.top = (parseInt(position.OffsetTop) + parseInt(sHeight)).toString() + "px"; } divObj.style.left = (parseInt(position.OffsetLeft) - parseInt(moveLeft)).toString() + "px"; } //如果遮盖iframe层不存在则创建 if (iframe == null) { //ie6下使用dom添加节点后无法控制某些属性, 所以将iframe放在一个div中,这样才可以用写html的方式添加. var tempIframeDiv = document.createElement("div"); tempIframeDiv.setAttribute("id", iframeDivId); document.body.appendChild(tempIframeDiv); var iframeString = "<iframe id=\"" + iframeId + "\" style=\"position: absolute; display:none; border-width:0px;\"></iframe>"; tempIframeDiv.innerHTML = iframeString; iframe = document.getElementById(iframeId); iframeDiv = document.getElementById(iframeDivId); } //使用遮盖层遮住select控件 if (iframe != null && iframeDiv != null) { iframeDiv.style.display = "block"; iframe.style.top = divObj.style.top; iframe.style.left = divObj.style.left; iframe.style.width = divObj.offsetWidth.toString() + "px"; iframe.style.height = divObj.offsetHeight.toString() + "px"; iframe.style.display = "block"; iframe.style.zIndex = divObj.style.zIndex - 1; } }
// 关闭图层 /* 参数说明: divId : 要隐藏的图层ID 用法与测试: ScriptHelperV2.closeDivCommon('testDiv'); */ scriptHelperV2.prototype.closeDivCommon = function(divId) { var iframeDivId = "tempIframeDiv" + divId; //iframe所在div的id var iframeId = "tempIframe" + divId; //iframe的id var divObj = document.getElementById(divId); //获得图层对象 if (divObj != null) { divObj.style.display = "none"; } var iframe = document.getElementById(iframeId); if (iframe != null) { iframe.style.display = "none"; } var iframeDiv = document.getElementById(iframeDivId); if (iframeDiv != null) { iframe.style.display = "none"; } }
四. 其他改进
和本系列文章第一版本的方法比较, showDivCommon方法还做了如下改进:
1. 将计算坐标的方法抽象出来:
//获取对象相对于Body对象的偏移量坐标.需要在Body元素加上position:relative, 并且保证任何父级元素都没有position:relative /* 参数说明: sObj : 要弹出图层的事件源 用法与测试: var sObj = document.getElementById("divId"); var position = ScriptHelperV2.getPosition(sObj); var sObjOffsetTop = parseInt( position.OffsetTop ); var sObjOffsetLeft = parseInt( position.OffsetLeft ); */ scriptHelperV2.prototype.getPosition = function(sObj) { var sObjOffsetTop = 0; //事件源的垂直距离 var sObjOffsetLeft = 0; //事件源的水平距离 /* 获取事件源对象的偏移量 */ var tempObj = sObj; //用于计算事件源坐标的临时对象 while (tempObj && tempObj.tagName.toUpperCase() != "BODY") { sObjOffsetTop += tempObj.offsetTop; sObjOffsetLeft += tempObj.offsetLeft; tempObj = tempObj.offsetParent; } tempObj = null; return { OffsetTop: sObjOffsetTop, OffsetLeft: sObjOffsetLeft }; }
2.由于经验浅薄, 原以为获取不到对象的高度和宽度, 经过学习发现可以使用OffsetWidth和OffsetHeight获取. 所以进一步规范了showDivCommon的参数.现在只传入两个参数使用时的坐标计算更加正确:
<a class="cursorHand" onclick="ScriptHelperV2.showDivCommon(this,'subMenu1');">Menu1</a>
五.实例
原ScriptHelper实现效果:
新ScriptHelperV2实现效果:注意已经遮盖住了select控件
实例代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>ScriptHelper 类测试页面 - ShowDivComon 显示弹出层方法</title> <!--<script src="https://files.cnblogs.com/zhangziqiu/ScriptHelper.js" type="text/javascript"></script>--> <script src="https://files.cnblogs.com/zhangziqiu/ScriptHelperV2.js" type="text/javascript"></script> <style type="text/css"> .cursorHand { cursor:pointer;} </style> </head> <body style="position:relative;"> <div style="height:300px;"></div> <!-- Main Menu --> <div > <a class="cursorHand" onclick="ScriptHelperV2.showDivCommon(this,'subMenu1');">Menu1</a> </div> <div> <select id="Select1" style="z-index:1;"> <option>123</option> <option>456</option> </select> </div> <!-- Sub Menu 1 --> <div id="subMenu1" style="position:absolute; display:none; background-color:#D7EFCD; border:solid 1px #000000; margin:0px; padding:5px; width:200px;z-index:100;"> <div>SubMent-1</div> <div>SubMent-2</div> <div>SubMent-3</div> <div>SubMent-4</div> <div>SubMent-5</div> <div>SubMent-6</div> </div> </body> </html>
六.打包下载地址
https://files.cnblogs.com/zhangziqiu/TestScriptHelper_ShowDivComon.rar
javascript文件下载地址:
https://files.cnblogs.com/zhangziqiu/ScriptHelperV2.js
七.相关经验和技巧
- 一个li对象的width设置为100px或者li的容器ul的width为100px, 在firefox下, li的offsetWidth永远为100, 超出的内容部分不会自动撑开li, 即使设置了overflow:visible仍然只是在li外部显示超出内容. 解决办法是可以li内再增加一个span对象放置内容文字, 这时获取span的offsetWidth为内容的长度而不是li的长度.
- 使用document.createElement方法创建的对象,在IE6下虽然可以获取到对象, 但是无法再设置他的宽度和高度. 也就是说createElement在IE和FF下均可用, 但是要想动态创建一个标签并且设置他的宽高,比如一个<iframe>, 可以使用写入HTML代码的方式,写入后可以获取对象并设置他的宽度和高度.
- 一个div,里面有一个iframe, 关闭时一定要先关闭iframe再关闭div, 否则在ie6中iframe会无法关闭.
八.总结
这篇文章没有太多技术含量, 高手们请见谅. 在改进中我学习到了新的知识.希望能和鸟儿们一起进步.
出处:http://www.cnblogs.com/zhangziqiu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。