Javascript多级菜单


本文示例源代码或素材下载

  一、开篇

  一直都苦于找不到合适的菜单,最近自己做了一个,感觉收获不小,拿出来分享。先看效果:

  

  

  

  

  

  二、原理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="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();


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3