项目需要一个js日期控件,现在找的js控件大小57kb,使用灵活但显得太臃肿。因此昨天花两个小时写了个小巧美观的日期控件,它看起来是这样的:

jquery时间日期范围选择 jquery日期范围选择控件_日期控件

外观有点山寨手机上的日期选择器,而且确定按钮设计的偏小,不过这都可以再改。

考虑到日期控件将作为一个浮动层出现在任何地方,因此在IE6下有可能遇到select无法遮挡的BUG,所以采用标准的div+iframe结构。8个按钮用了8个LI元素。HTML代码如下:

<div class="colordate">
<iframe frameborder="0"></iframe>
<ul>
<li class="colordate_up"></li>
<li class="colordate_up"></li>
<li class="colordate_up"></li>
<li id="colordate_ok"></li>
<li class="colordate_down colordate_sel"></li>
<li class="colordate_down"></li>
<li class="colordate_down"></li>
<li id="colordate_cancel"></li>
</ul>
</div>

类名有点繁琐,主要是考虑到控件的使用环境有潜在的重名或CSS级别冲突。接下来要用CSS实现各个按钮的相对位置,以及把文字定位到上下两个LI之间。

先是外围元素的样式:

.colordate{
        width:158px;
        height:45px;
        position:absolute;
        padding:1px;
        z-index:9999;
        background-color:#BFE2FF;
        display:none;
}
    .colordate ul{
        margin:0;
        padding:0;
        list-style-type:none;
}
.colordate iframe{
        background-color:#BFE2FF;
        width:158px;
        height:45px;
        position:absolute; 
        z-index:-1;
}

关于div+iframe结构,网上给的很多css代码是不完整的,无法保证在IE6下的显示效果,必须要设置iframe的宽高为div的宽高,可以有1px误差,可能是边框引起的,但绝对不能太小或太大。背景色可有可无。z-index必须是-1,而不能直接隐藏,那样iframe就失效了。

然后是LI的统一定义:

.colordate li{
        float:left;
        margin-right:1px;
        display:inline-block;
        width:44px;
        height:22px;
        background:url("../pic/common/date.gif") no-repeat left top;
}

background默认是上三个按钮的样式,而date.gif是包含所有图标样式的大图,因此对下三个按钮和两个功能按钮要单独定义background-position。

.colordate li.colordate_down{
        font-size:15px;
        text-align:center;
        font-weight:bold; 
        margin-top:-11px!important;
        margin-top:-9px;
        height:33px;
        background-position:0 -22px!important;
        background-position:0 -24px;
}
.colordate #colordate_ok{
        width:22px;
        background-position:-44px 0;
}
    .colordate #colordate_cancel{
        width:22px;
        background-position:-44px -33px;
}

把文字样式定义在下三个按钮里,这样方便通过调整按钮的margin-top来让文字向上偏移。(这样造成一个BUG,后面说)

IE6里文字的偏移量有2px误差,所以用!important定义了两种偏移。

最后对按下的按钮定义样式,只是一个背景偏移:

.colordate li.colordate_usel{
        background-position:-66px 0;
}
    .colordate li.colordate_dsel{
        background-position:-66px -22px!important;
        background-position:-66px -24px;
}

由于更改了下排按钮的margin-top,所以在date.gif里还要留相应的空白,来保证按钮图像不会重叠。

这张图是下面这样的,颜色有点难看 :(

jquery时间日期范围选择 jquery日期范围选择控件_jQuery_02

然后就是js部分,它分成三个部分:

1.动态地创建这个div+iframe框架

2.相应鼠标事件,动态更新日期,同时保证时期值有效

3.相应input的点击,显示日期控件。

第一部分代码比较简单

var now=new Date();//今日
        var year=now.getFullYear();
var month=now.getMonth()+1;
var day=now.getDate();
//创建控件
        var div=document.createElement("div");
        div.className="colordate";
        div.innerHTML='<iframe frameborder="0"></iframe><ul><li class="colordate_up"></li>\
        <li class="colordate_up"></li><li class="colordate_up"></li><li id="colordate_ok"></li>\
        <li class="colordate_down">'+year+'</li><li class="colordate_down">'+month+'</li>\
        <li class="colordate_down">'+day+'</li><li id="colordate_cancel"></li></ul>';
        document.body.appendChild(div);

然后要计算要绑定的input框位置,从而确定日期框的位置,一般日期框都显示在input的正下方,代码如下,这里用jQuery来算偏移。

var $div=$(div);    
var pos=this.offset();
        pos.top+=this.height();
        $div.css({left:pos.left+"px",top:pos.top+"px"}); //定位

第二部分代码包括鼠标按下和抬起两个函数,在按下函数中,要确定是哪个按钮被按下,更换背景,以及显示日期。由于显示日期部分6个翻日期按钮都要用到,所以独立出来做函数:

var setDate=function(){
if(day<=0) day=32;
if(month<=0) month=12;
if(month>=13) month=1;
switch(month){
case 1:case 3:case 5:case 7:case 8:case 10:case 12:
if(day>=32) day=31;
break;
case 4:case 6:case 9:case 11:
if(day>=31) day=30;
break;
default: //2
                    if(day>28){  //非4倍数 或者 是100倍数 但不是400倍数
                        if(year%4!=0 ||(year%400!=0 && year%100==0))    day=28;
else day=29;
                    }
break;
            }
            $down.eq(0).html(year).end()
                .eq(1).html(month).end()
                .eq(2).html(day);//显示日期
        };

2-4行代码产生日期循环效果。这里没有做年月日联动效果,因为我想,用户不会按365下“日”按钮来翻到下一年,所以这样设计不会带来太大用户体验问题。

然后对应上排三个按钮的鼠标落下事件,代码如下:

var index=$(event.target).index();    
      event.target.className+=" colordate_usel";
                    index>1?day++:(index==1?month++:year++);
                    setDate();

抬起事件里简单地删除添加的那个类即可。这里出现一个问题,用户翻日期必须一次次地点击,而且我将初始日期设为当前日期。如果用户要选择的是他的生日,那翻日期会非常麻烦。因此有必要让用户可以按住按钮不放,日期不停地翻动。实现这个效果需要定时器。

trigerId=setTimeout(function(){
                        timeId=setInterval(function(){
                            index>1?day--:(index==1?month--:year--);
                            setDate();
                        },100);
                    },250);

外侧定时器避免用户直接进入快速翻动模式,250ms间隔是实验多次得出的,效果比较理想。内侧定时器的100ms间隔对于短暂地翻10页比较好,但如果用户要翻的是40页,(比如用户是1970年生人)那就需要4秒时间,这显然太长了。合理的做法是让这个间隔逐渐缩小,产生翻动加快的效果。由于月日有循环效果,用户翻任何一个“日”不超过15次点击,或者1.5秒。所以只需对年的翻动使用加速效果。

trigerId=setTimeout(function(){
                        timeId=setTimeout(function(){
                            index>1?day++:(index==1?month++:year++);
                            setDate();
var intv=Math.floor(Math.abs(now.getFullYear()-year)/8)*10; 
if(intv<=40)
                                timeId=setTimeout(arguments.callee,100-intv);
else
                                timeId=setTimeout(arguments.callee,40);
                        },100);
                    },250);

代码中,我用setTimeout代替了Interval,计算时间与当前时间的间隔,动态加速,40年的间隔开始进入最快加速状态,这样,用户翻40页只需要2.8秒。

这样在鼠标抬起事件中,还要清除两个定时器。

代码至此,主流浏览器都没有问题了。但是在万恶的IE6里,出现了按钮背景图切换迟缓的问题。我仔细检查了代码,更换了可能引起性能问题的代码,用计时器算了每段代码的事件,都不是问题的源头。最后发现这居然是IE6的背景图重复载入BUG。

解决办法是,用js强制缓存:

try{
document.execCommand('BackgroundImageCache', false, true); 
}catch(e){};

至此,IE6的性能问题有所改善,但还没达到Firefox的流畅程度。

上文提到,修改margin-top导致了一个bug,那就是,下翻按钮的点中区域也提高了11px。此问题暂时无解,除非我能找到一个更好的处理文字居中的办法,同时不引入更多标签。想到了再写一篇文章吧。

---------4月13日更新----------

旧代码有个BUG,在计算div偏移量时,如果初始页面div的父元素是隐藏的,那么算出的偏移量就是错误的,正确的方法是在日期控件第一次显示时计算。


this.click(function(){
if(pos==null){
                pos=input.offset();
                pos.top+=input.height();
                $div.css({left:pos.left+"px",top:pos.top+"px"});
            }
            $div.show();
        });