discoverx

博客园 首页 新随笔 联系 订阅 管理

一、开篇

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

二、原理

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

四、下载

        点此下载示例

posted on 2008-11-07 13:24  discoverx  阅读(369)  评论(0编辑  收藏  举报