一、开篇
一直都苦于找不到合适的菜单,最近自己做了一个,感觉收获不小,拿出来分享。先看效果:
二、原理
$( " 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();
});
三、代码
Menu类
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();
});
}
});
MenuItem类
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();
四、下载
点此下载示例
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架