Javascript多级菜单
2010-09-14 13:24:38 来源:WEB开发网本文示例源代码或素材下载
一、开篇
一直都苦于找不到合适的菜单,最近自己做了一个,感觉收获不小,拿出来分享。先看效果:
二、原理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上面),应该如何做呢?
$("childItems").onmouseover=function(e){
e=e||window.event;
vartarget=e.target||e.srcElement;
varrelatedTarget=e.relatedTarget||e.fromElement;
if(!$(relatedTarget).descendantOf(this)&&$(relatedTarget)!=this){
clearTimeout(timeoutId);
timeoutId=null;
}
}
$("childItems").onmouseout=function(e){
e=e||window.event;
vartarget=e.target||e.srcElement;
varrelatedTarget=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上,则弹出子菜单;
鼠标移开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();
},
vartemp=self;
while(temp){
temp.open();
temp=temp.parent;
}
2、 关闭所有兄弟菜单的子菜单,因为当前菜单可能会弹出自己的子菜单,所以这个时候要关闭所有的兄弟菜单的子菜单。这是通过以下代码来实现的:
varitems=self.parent?self.parent.items:self.menu.rootItems;
items.each(function(item){
if(self!=item)
item.closeAll();
});
三、代码
Menu类
varMenu=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();
});
}
});
MenuItem类
varMenuItem=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
vartemp=childItem;
while(temp.parent){
if(temp.parent==this)
returntrue;
temp=temp.parent;
}
returnfalse;
},
topItem:function(){
vartemp=this;
while(temp){
if(temp.depth==0)
returntemp;
temp=temp.parent;
}
returntemp;
},
render:function(){
varself=this;
functionelementMouseOver(e){
//关闭所有兄弟菜单
varitems=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"
});
}
//
vartemp=self;
while(temp){
temp.open();
temp=temp.parent;
}
//self.childMenu.show();
self.menu.currentItem=self;
}
this.element.observe("mouseover",elementMouseOver.bindAsEventListener());
functionelementMouseOut(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);
functionchildMenuMouseOver(e){
vartarget=e.element();
varrelatedTarget=e.relatedTarget||e.fromElement;
if(!$(relatedTarget).descendantOf(self.childMenu)&&$(relatedTarget)!=self.childMenu){
self.clearCloseTimeout();
}
}
this.childMenu.observe("mouseover",childMenuMouseOver.bindAsEventListener());
functionchildMenuMouseOut(e){
vartarget=e.element();
varrelatedTarget=e.relatedTarget||e.toElement;
if(!$(relatedTarget).descendantOf(self.childMenu)&&$(relatedTarget)!=self.childMenu){
//关闭所有的菜单
vartemp=self;
while(temp){
temp.timeoutClose();
temp=temp.parent;
}
}
}
this.childMenu.observe("mouseout",childMenuMouseOut.bindAsEventListener());
$A(this.items).each(function(item,index){
item.render();
varli=$(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(){
varself=this;
this.clearCloseTimeout();
this.closeTimeoutId=setTimeout(close,500);//这里不能直接用this.close或者self.close
functionclose(){
self.close();
}
}
});
使用方法
functioncreateItemElement(text){
vara=document.createElement("a");
a.href="javascript:void(0);";
vartextNode=document.createTextNode(text);
a.appendChild(textNode);
returna;
}
varmenu=newMenu("childMenu");
//item1
varitem1=newMenuItem($("item1"));
menu.addItem(item1);
varitem11=newMenuItem(createItemElement("item1-1"));
item1.addItem(item11);
varitem12=newMenuItem(createItemElement("item1-2"));
item1.addItem(item12);
// varimg=newImage();
// img.src=http://tech.ddvip.com/2008-11/"botton_gif_080.gif";
// vara=document.createElement("a");
// a.href="javascript:void(0);";
// a.appendChild(img);
varitem13=newMenuItem(createItemElement("item1-3"));
item1.addItem(item13);
varitem131=newMenuItem(createItemElement("item1-3-1"));
item13.addItem(item131);
varitem1311=newMenuItem(createItemElement("item1-3-1-1"));
item131.addItem(item1311);
varitem132=newMenuItem(createItemElement("item1-3-2"));
item13.addItem(item132);
varitem14=newMenuItem(createItemElement("item1-4"));
item1.addItem(item14);
varitem141=newMenuItem(createItemElement("item1-4-1"));
item14.addItem(item141);
varitem1411=newMenuItem(createItemElement("item1-4-1-1"));
item141.addItem(item1411);
varitem142=newMenuItem(createItemElement("item1-4-2"));
item14.addItem(item142);
//item2
varitem2=newMenuItem($("item2"));
menu.addItem(item2);
varitem21=newMenuItem(createItemElement("item2-1"));
item2.addItem(item21);
varitem22=newMenuItem(createItemElement("item2-2"));
item2.addItem(item22);
varitem23=newMenuItem(createItemElement("item2-3"));
item2.addItem(item23);
menu.render();
Tags:Javascript 多级 菜单
编辑录入:爽爽 [复制链接] [打 印]更多精彩
赞助商链接