一、开篇
上次写了一个没有任何效果的多级菜单,有朋友说直接用CSS就可以实现,所以就继续加工了一下,做了两个用CSS不能实现的菜单,效果如下:
渐变多级菜单
滑动多级菜单
二、原理
修改了一下上一篇中的代码,再次总结一下原理:
主要是要响应4个事件:菜单项(MenuItem.element,是一个li)的mouseover和mouseout以及子菜单MenuItem.childMenu,是一个ul)的mouseover和mouseout事件。这几种事件都可能因为其子元素的冒泡而重复发生,可以利用上一篇介绍的方法,判断relatedTarget来防止这种事情的发生。
var target = e.element();
var relatedTarget = e.relatedTarget || e.fromElement;
if(!relatedTarget)return;
if($(relatedTarget).descendantOf(self.element) || $(relatedTarget) == self.element)
return;
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方法如下:
open:function(){//立即打开当前菜单
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.liFocus(this);
if(this.childMenu){
this.childMenu.show();
writeLog(this.name + " childMenu show");
}
this.isClosed = false;
},
close:function(){//立即关闭当前菜单
if(this.isClosed)return;//保证不重复关闭
this.clearCloseTimeout();
this.menu.liBlur(this);
if(this.childMenu){
this.childMenu.hide();
writeLog(this.name + " childMenu hide");
}
this.isClosed = true;
},
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不一样)。
对于渐变菜单,就是设置一下透明度:
open:function(){//立即打开当前菜单
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.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;
}
close:function(){//立即关闭当前菜单
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;
}
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如下:
open:function(){//立即打开当前菜单
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.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;
}
close:function(){//立即关闭当前菜单
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;
}
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;
}