zoukankan      html  css  js  c++  java
  • 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;
        
    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上,则弹出子菜单;
      鼠标移开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();

    四、下载

            点此下载示例

  • 相关阅读:
    6. Flask请求和响应
    5. Flask模板
    FW:Software Testing
    What is the difference between modified duration, effective duration and duration?
    How to push master to QA branch in GIT
    FTPS Firewall
    Query performance optimization of Vertica
    (Forward)5 Public Speaking Tips That'll Prepare You for Any Interview
    (转)The remote certificate is invalid according to the validation procedure
    Change
  • 原文地址:https://www.cnblogs.com/discoverx/p/1328791.html
Copyright © 2011-2022 走看看