一、开篇
上次写了一个没有任何效果的多级菜单,有朋友说直接用CSS就可以实现,所以就继续加工了一下,做了两个用CSS不能实现的菜单,效果如下:
渐变多级菜单
滑动多级菜单
二、原理
修改了一下上一篇中的代码,再次总结一下原理:
主要是要响应4个事件:菜单项(MenuItem.element,是一个li)的mouseover和mouseout以及子菜单MenuItem.childMenu,是一个ul)的mouseover和mouseout事件。这几种事件都可能因为其子元素的冒泡而重复发生,可以利用上一篇介绍的方法,判断relatedTarget来防止这种事情的发生。
var relatedTarget = e.relatedTarget || e.fromElement;
if ( ! relatedTarget) return ;
if ($(relatedTarget).descendantOf(self.element) || $(relatedTarget) == self.element)
return ;
- MenuItem.element.mouseover事件:鼠标移动到菜单项时,先要关闭同一级的其他菜单,然后要设置当前子菜单的位置,然后打开当前的子菜单,并且需要打开当前菜单之上的所有菜单;
- MenuItem.element.mouseout事件:关闭当前菜单的子菜单;
- MenuItem.childMenu.mouseover事件:清除当前菜单及之上的所有菜单的关闭延时,关闭延时是防止鼠标移出菜单过后菜单马上消失;
- MenuItem.childMenu.mouseout事件:开始关闭当前菜单以及之上的所有菜单的延时;
打开子菜单和关闭子菜单是通过MenuItem的open和close方法来实现的,但是响应上面四个鼠标事件的过程中,可能会反复调用某个菜单的open和close方法,这样给菜单制作带来了很大的麻烦,尤其是对于渐变和滑动的菜单,重复的open和close会导致菜单产生很多怪异的行为。所以,在MenuItem里面添加了closed这个属性来标志当前子菜单的状态。在open和close调用的时候都需要先判断,如果已经打开了就不能再次打开但是可以关闭,反之亦然。
最简单的open和close方法如下:
if ( ! this .isClosed) return ; // 保证不重复打开
this .clearCloseTimeout();
this .menu.liFocus( this );
if ( this .childMenu){
this .childMenu.show();
writeLog( this .name + " childMenu show " );
}
this .isClosed = false ;
},
if ( this .isClosed) return ; // 保证不重复关闭
this .clearCloseTimeout();
this .menu.liBlur( this );
if ( this .childMenu){
this .childMenu.hide();
writeLog( this .name + " childMenu hide " );
}
this .isClosed = true ;
},
对于渐变菜单和滑动菜单,就可以集中来解决做渐变和滑动效果,其他的变化不是很大(滑动菜单的render不一样)。
对于渐变菜单,就是设置一下透明度:
this .clearCloseTimeout();
if ( ! this .isClosed) return ; // 保证不重复打开
this .menu.liFocus( this );
if ( this .childMenu){
clearInterval( this .fadeInIntervalId);
clearInterval( this .fadeOutIntervalId);
var self = this ;
var init = 0 ;
this .childMenu.setOpacity(init);
this .childMenu.show();
function fadeIn(){
init += 0.1 ;
if (init >= 1 )
init = 1 ;
self.childMenu.setOpacity(init);
if (init == 1 ){
clearInterval(self.fadeInIntervalId);
// self.isClosed = false;
}
}
this .fadeInIntervalId = setInterval(fadeIn, 30 );
writeLog( this .name + " childMenu show " );
}
this .isClosed = false ;
}
this .clearCloseTimeout();
if ( this .isClosed) return ; // 保证不重复关闭
this .menu.liBlur( this );
if ( this .childMenu){
clearInterval( this .fadeInIntervalId);
clearInterval( this .fadeOutIntervalId);
var self = this ;
var init = 1 ;
this .childMenu.setOpacity(init);
function fadeOut(){
init -= 0.1 ;
if (init <= 0 )
init = 0 ;
self.childMenu.setOpacity(init);
if (init == 0 ){
clearInterval(self.fadeOutIntervalId);
self.childMenu.hide();
}
}
this .fadeOutIntervalId = setInterval(fadeOut, 10 );
writeLog( this .name + " childMenu hide " );
}
this .isClosed = true ;
}
对于滑动菜单,原理可以参看以前的介绍滑动菜单的文章:滑动菜单(一) 、 滑动菜单(二)
滑动菜单的open和close如下:
this .clearCloseTimeout();
if ( ! this .isClosed) return ; // 保证不重复打开
this .menu.liFocus( this );
if ( this .childMenu){
clearInterval( this .slideInIntervalId);
clearInterval( this .slideOutIntervalId);
this .childMenuContainer.show();
var self = this ;
var start = 0 ;
if ( this .depth == 0 )
start = parseInt( this .childMenu.getStyle( " top " ));
else
start = parseInt( this .childMenu.getStyle( " left " ));
var end = 0 ;
function slideIn(){
var step = Math.round((end - start) / 5);
start += step;
if (start >= end || step == 0 )
start = end;
if (self.depth == 0 )
self.childMenu.setStyle({
" top " :start + " px "
});
else
self.childMenu.setStyle({
" left " :start + " px "
});
if (start == end){
clearInterval(self.slideInIntervalId);
self.isClosed = false ;
}
}
this .slideInIntervalId = setInterval(slideIn, 15 );
writeLog( this .name + " childMenu show " );
}
this .isClosed = false ;
}
this .clearCloseTimeout();
if ( this .isClosed) return ; // 保证不重复关闭
this .menu.liBlur( this );
if ( this .childMenu){
clearInterval( this .slideInIntervalId);
clearInterval( this .slideOutIntervalId);
var self = this ;
var start = 0 ;
if ( this .depth == 0 )
start = parseInt( this .childMenu.getStyle( " top " ));
else
start = parseInt( this .childMenu.getStyle( " left " ));
var end = 0 ;
if ( this .depth == 0 )
end = - this .childMenu.getHeight();
else
end = - this .childMenu.getWidth();
function slideOut(){
var step = Math.round((start - end) / 5);
start -= step;
if (start <= end || step == 0 )
start = end;
if (self.depth == 0 )
self.childMenu.setStyle({
" top " :start + " px "
});
else
self.childMenu.setStyle({
" left " :start + " px "
});
if (start == end){
clearInterval(self.slideOutIntervalId);
// self.isClosed = false;
self.childMenuContainer.hide();
}
}
this .slideOutIntervalId = setInterval(slideOut, 30 );
writeLog( this .name + " childMenu hide " );
}
this .isClosed = true ;
}