项目需要一个js日期控件,现在找的js控件大小57kb,使用灵活但显得太臃肿。因此昨天花两个小时写了个小巧美观的日期控件,它看起来是这样的:
外观有点山寨手机上的日期选择器,而且确定按钮设计的偏小,不过这都可以再改。
考虑到日期控件将作为一个浮动层出现在任何地方,因此在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里还要留相应的空白,来保证按钮图像不会重叠。
这张图是下面这样的,颜色有点难看 :(
然后就是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();
});