一、开篇
一直都苦于找不到合适的菜单,最近自己做了一个,感觉收获不小,拿出来分享。先看效果:
二、原理
-
1、关于鼠标事件
首先说一下mouseover和mouseout这两个事件,在IE和其他浏览器有一些差别。
在IE中,当发生mouseover事件的时候,e.srcElement可以获得鼠标移入的元素,e.fromElement可以获得鼠标是从哪个元素移入的,e.toElement就是e.srcElement;
在IE中,当发生mouseout事件的时候,e.srcElement可以获得鼠标移出的元素,e.fromElement和e.srcElement是一样的,e.toElement可以获得鼠标移动到当前的元素;
在DOM中,mouseover和mouseout所发生的元素可以通过e.target来访问,相关元素是通过e.relatedTarget来访问的(在mouseover中相当于IE的e.fromElement,在mouseout中相当于IE的e.toElement);
对于这样一种标签的嵌套关系,对ul注册mouseover和mouseout事件,鼠标从ul外面移入到最里面的a上面,会出现什么情况?
答案是会出现三次mouseover和两次mouseout,虽然没有直接给li和a注册事件,但是由于事件的冒泡,也会被ul注册的mouseover和mouseout事件响应。这样一来,整过过程就是这样的:
在IE中order type srcElement fromElement toElement 1 mouseover ul 其他 ul 2 mouseout ul ul li 3 mouseover li ul li 4 mouseout li li a 5 mouseover a li a
在DOM中
order type target relatedTarget 1 mouseover ul 其他 2 mouseout ul li 3 mouseover li ul 4 mouseout li a 5 mouseover a li
如果我想在移入ul的时候,只响应一次mouseover和mouseout(无论移动到ul里面的li还是里面的a上面),应该如何做呢?
e = e||window.event;
var target = e.target||e.srcElement;
var relatedTarget = e.relatedTarget || e.fromElement;
if(!$(relatedTarget).descendantOf(this) && $(relatedTarget) != this){
clearTimeout(timeoutId);
timeoutId = null;
}
}
$("childItems").onmouseout = function(e){
e = e||window.event;
var target = e.target||e.srcElement;
var relatedTarget = e.relatedTarget ||e.toElement;
if(!$(relatedTarget).descendantOf(this) && $(relatedTarget) != this){//如果relatedTarget不是ul本身或者不是ul的子元素
close();
}
}
mouseover事件发生的时候,判断relatedTarget(IE中的fromElement),如果relatedTarget是其他元素(不是ul的子元素也不是ul本身,表示鼠标从外面移入ul)才会响应,而在ul移入li或者li移入a的时候就不会相应。
同理,发生mouseout的时候,判断relatedTarget(IE中的toElement),如果relatedTarget是其他元素(不是ul的子元素也不是ul本身,表示鼠标从ul或者其子元素移出)才会响应,而在a移出到li或者li移出到ul的时候就不会相应。
-
2、简单菜单
鼠标移开button,则开始关闭菜单的setTimeout;
在timeout还未到的时候移入子菜单,则clearTimeout;
鼠标移出子菜单,则开始关闭菜单的setTimeout;
此时鼠标如果再移动到button上或者是移回到子菜单上,都需要clearTimeout,所以的mouseover也要先clearTimeout;
-
3、多级菜单的逻辑
在途中,对于item1-3这个MenuItem,depth属性(深度)为1(根菜单的深度为0,其子菜单深度为1,以此类推);childMenu是子菜单,当鼠标移动到MenuItem上面的时候,其子菜单则会显示;items属性是子菜单的MenuItem集合;parent属性是对上一级的MenuItem的引用。
多级菜单比起简单菜单,各个MenuItem之间的逻辑关系是非常重要的:
总的来说,鼠标移出任何一个菜单,都要开始计时关闭所有的菜单
当鼠标移入任何一个菜单,除了显示他的子菜单(如果有的话)之外,还需要做两件事情:
1、 显示他的所有父菜单,如果父菜单已经显示的话,则清除计时关闭。这是通过一下代码来实现的
open:function(){
this.clearCloseTimeout();
if(this.childMenu)
this.childMenu.show();
},var temp = self;
while(temp){
temp.open();
temp = temp.parent;
}
2、 关闭所有兄弟菜单的子菜单,因为当前菜单可能会弹出自己的子菜单,所以这个时候要关闭所有的兄弟菜单的子菜单。这是通过以下代码来实现的:
var items = self.parent?self.parent.items:self.menu.rootItems;
items.each(function(item){
if(self != item)
item.closeAll();
});
三、代码
var Menu = Class.create({
initialize:function(childMenuClassName){
this.rootItems = [];
this.currentItem = null;//当前展开的MenuItem
this.childMenuClassName = childMenuClassName;
},
addItem:function(rootItem){
rootItem.depth = 0;
rootItem.parent = null;
rootItem.menu = this;
this.rootItems.push(rootItem);
},
render:function(){
this.rootItems.each(function(item,index){
item.render();
});
}
});
var MenuItem = Class.create({
initialize:function(element){
this.element = $(element);
this.items = null;
this.closeTimeoutId = null;
this.menu = null;
this.childMenu = null;
this.depth = 0;
this.parent = null;
},
addItem:function(menuItem){
if(!this.items)
this.items = [];
menuItem.parent = this;
menuItem.depth = this.depth + 1;
menuItem.menu = this.menu;
this.items.push(menuItem);
},
isParentOf:function(childItem){//判断当前item是不是childItem的parent
var temp = childItem;
while(temp.parent){
if(temp.parent == this)
return true;
temp = temp.parent;
}
return false;
},
topItem:function(){
var temp = this;
while(temp){
if(temp.depth == 0)
return temp;
temp = temp.parent;
}
return temp;
},
render:function(){
var self = this;
function elementMouseOver(e){
//关闭所有兄弟菜单
var items = self.parent?self.parent.items:self.menu.rootItems;
items.each(function(item){
if(self != item)
item.closeAll();
});
self.clearCloseTimeout();
if(self.depth == 0){
self.childMenu.setStyle({
"top":self.element.cumulativeOffset().top + self.element.getHeight() + "px",
"left":self.element.cumulativeOffset().left + "px"
});
}else{
self.childMenu.setStyle({
"top":self.element.cumulativeOffset().top + "px",
"left":self.element.cumulativeOffset().left + self.element.getWidth() + "px"
});
}
//
var temp = self;
while(temp){
temp.open();
temp = temp.parent;
}
//self.childMenu.show();
self.menu.currentItem = self;
}
this.element.observe("mouseover",elementMouseOver.bindAsEventListener());
function elementMouseOut(e){
//关闭当前的子菜单
self.timeoutClose();
}
this.element.observe("mouseout",elementMouseOut.bindAsEventListener());
this.childMenu = $(document.createElement("ul"));
this.childMenu.setStyle({
"position":"absolute",
"display":"none",
"top":"0px",
"left":"0px",
"margin":"0px"
});
if(this.menu.childMenuClassName)
this.childMenu.addClassName(this.menu.childMenuClassName);
function childMenuMouseOver(e){
var target = e.element();
var relatedTarget = e.relatedTarget || e.fromElement;
if(!$(relatedTarget).descendantOf(self.childMenu) && $(relatedTarget) != self.childMenu){
self.clearCloseTimeout();
}
}
this.childMenu.observe("mouseover",childMenuMouseOver.bindAsEventListener());
function childMenuMouseOut(e){
var target = e.element();
var relatedTarget = e.relatedTarget || e.toElement;
if(!$(relatedTarget).descendantOf(self.childMenu) && $(relatedTarget) != self.childMenu){
//关闭所有的菜单
var temp = self;
while(temp){
temp.timeoutClose();
temp = temp.parent;
}
}
}
this.childMenu.observe("mouseout",childMenuMouseOut.bindAsEventListener());
$A(this.items).each(function(item,index){
item.render();
var li = $(document.createElement("li"));
li.appendChild(item.element);
self.childMenu.appendChild(li);
});
if(!this.items)
return;
document.body.appendChild(this.childMenu);
},
open:function(){
this.clearCloseTimeout();
if(this.childMenu)
this.childMenu.show();
},
close:function(){
if(this.childMenu)
this.childMenu.hide();
},
closeAll:function(){
this.close();
if(!this.items)return;
this.items.each(function(item){
item.closeAll();
});
},
clearCloseTimeout:function(){
clearTimeout(this.closeTimeoutId);
this.closeTimeoutId = null;
},
timeoutClose:function(){
var self = this;
this.clearCloseTimeout();
this.closeTimeoutId = setTimeout(close,500);//这里不能直接用this.close或者self.close
function close(){
self.close();
}
}
});
function createItemElement(text){
var a = document.createElement("a");
a.href = "javascript:void(0);";
var textNode = document.createTextNode(text);
a.appendChild(textNode);
return a;
}
var menu = new Menu("childMenu");
//item1
var item1 = new MenuItem($("item1"));
menu.addItem(item1);
var item11 = new MenuItem(createItemElement("item 1-1"));
item1.addItem(item11);
var item12 = new MenuItem(createItemElement("item 1-2"));
item1.addItem(item12);
// var img = new Image();
// img.src = "botton_gif_080.gif";
// var a = document.createElement("a");
// a.href = "javascript:void(0);";
// a.appendChild(img);
var item13 = new MenuItem(createItemElement("item 1-3"));
item1.addItem(item13);
var item131 = new MenuItem(createItemElement("item 1-3-1"));
item13.addItem(item131);
var item1311 = new MenuItem(createItemElement("item 1-3-1-1"));
item131.addItem(item1311);
var item132 = new MenuItem(createItemElement("item 1-3-2"));
item13.addItem(item132);
var item14 = new MenuItem(createItemElement("item 1-4"));
item1.addItem(item14);
var item141 = new MenuItem(createItemElement("item 1-4-1"));
item14.addItem(item141);
var item1411 = new MenuItem(createItemElement("item 1-4-1-1"));
item141.addItem(item1411);
var item142 = new MenuItem(createItemElement("item 1-4-2"));
item14.addItem(item142);
//item2
var item2 = new MenuItem($("item2"));
menu.addItem(item2);
var item21 = new MenuItem(createItemElement("item 2-1"));
item2.addItem(item21);
var item22 = new MenuItem(createItemElement("item 2-2"));
item2.addItem(item22);
var item23 = new MenuItem(createItemElement("item 2-3"));
item2.addItem(item23);
menu.render();