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();

    四、下载

            点此下载示例

  • 相关阅读:
    Java利用QRCode.jar包实现二维码编码与解码
    javax.servlet不存在问题的解决
    订单号生成规则
    Koa处理url
    网站追踪技术:前端持久化evercookie
    网站追踪技术:“帆布指纹识别”canvas fingerprinting
    Typescript类、命名空间、模块
    TypeScript 基础类型、变量声明、函数、联合类型、接口
    webpack中的hash、chunkhash、contenthash区别
    不刷新网页修改url链接:history.pushState()和history.replaceState()新增、修改历史记录用法介绍
  • 原文地址:https://www.cnblogs.com/discoverx/p/1328791.html
Copyright © 2011-2022 走看看