Javascript公共脚本库系列(四) 改进的弹出层脚本

一.摘要

本篇文章并没有为系列文中构造的轻量级脚本库添加新的方法, 而是改进了原有弹出浮动层的方法. 对方法中获取位置的函数重构出来, 为弹出层自动添加iframe遮盖层以实现IE6下遮住<Select>控件. 又放在此系列文章中也是对自己学习过程的一次记录.

二.关于弹出层无法遮盖select的问题

在IE6下存在一个Bug: 如果弹出层是一个div, 并且在弹出层下方有一个<select>下拉框控件, 则div无论z-index值如何设置都无法遮盖select控件.如图:

image

目前有两种解决办法:

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实现效果:

image

新ScriptHelperV2实现效果:注意已经遮盖住了select控件

image

实例代码:

<!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会无法关闭.

八.总结

这篇文章没有太多技术含量, 高手们请见谅. 在改进中我学习到了新的知识.希望能和鸟儿们一起进步.

posted @ 2009-02-24 14:55  ziqiu.zhang  阅读(5629)  评论(8编辑  收藏  举报