二级菜单练习

  • 练习要求
  • 实现步骤
  • 实现


练习要求

实现二级菜单效果,即打开一个一级菜单时其他一级菜单不能同时打开,但可以同时关闭所有菜单,且要求菜单的打开和关闭有动画效果。(结合定时器和类的操作)

实现效果如图:

Android 实现二级菜单联动 二级菜单怎么实现_Android 实现二级菜单联动


Android 实现二级菜单联动 二级菜单怎么实现_动画效果_02


解决思路:

1、当表示一级菜单的div有collapsed类时,菜单处于折叠状态,没有collapsed类时,处于展开状态。

2、为二级菜单的标题绑定单击响应函数。

实现步骤

首先,搭建好基础结构和设置相关样式。
其次,创建相关工具函数:
类操作的相关函数

//添加指定class属性
function addClass(obj,ch){
	if(!hasClass(obj,ch)){
		obj.className+=" "+ch;
	}
}

//判断是否含有指定class属性
function hasClass(obj,ch){
	var reg=new RegExp("\\b"+ch+"\\b");
	return reg.test(obj.className);
}

//删除指定class属性
function removeClass(obj,ch){
	var reg=new RegExp("\\b"+ch+"\\b");
	obj.className=obj.className.replace(reg,"");
}

//切换指定class属性
function toggleClass(obj,ch){
	//先判断是否有指定class属性
	if(hasClass(obj,ch)){
		//含有,则删除
		removeClass(obj,ch);
	}else{
		//没有,则添加
		addClass(obj,ch);
	}
}

动画效果的函数

//创建函数控制元素移动
function move(obj , attr, target ,speed,callback){
	//为防止重复开启定时器,要先关闭原有的定时器
	clearInterval(obj.timer);
	var current=getStyle(obj,attr);
	if(parseInt(current)>target){
		speed=-speed;
	}
	
	//开启定时器
	//向对象中添加timer属性用于单独保存自己的定时器标识
	obj.timer=setInterval(function(){
		//获取原来的attr值
		var oldValue=getStyle(obj,attr);
		//在原值的基础上修改attr值
		var newValue=parseInt(oldValue)+speed;
		//判断是否到达目标样式
		if((speed<0&&newValue<target)||(speed>0&&newValue>target)){
			newValue=target;
		}
		//修改元素对象的样式
		obj.style[attr]=newValue+"px";
		//元素到达目标位置时,关闭定时器
		if(newValue==target){
			clearInterval(obj.timer);
			//判断是否传入回调函数的实参,如果有回调函数,则在动画执行完毕调用回调函数;如果没有,则不执行回调函数
			callback&&callback();
		}
	},30);
}

具体实现步骤:
1.获取菜单标题
2.为每一个菜单标题绑定单击响应函数
3. 在响应函数中获取当前菜单的父元素div
4. 调用toggleClass()函数为当前div切换collapsed类
5. 打开一个菜单时,要关闭之前打开的菜单
(1)在绑定单击响应函数前,定义一个变量openDiv保存当前打开的菜单
(2)判断当前打开的菜单是否是openDiv,如果不是,则关闭当前菜单;就是当前打开的菜单,则不关闭
(3)重新将openDiv修改为当前打开的菜单
这里我们会遇到一个隐含的问题,如果不注意忽视了的话会出错:
存在的隐含问题:
* (1)、因为openedDiv初始值是第一个菜单,当我们第一次点击第一个菜单时为关闭第一个菜单,此时添加了collapsed属性
* (2)、与此同时,我们又为openedDiv重新赋值为第一个菜单
* (3)、所以当先关闭了第一个菜单,然后我们又想要打开第一个菜单时,此时第一个菜单为关闭状态,执行流程是:
* 先为第一个菜单移除collapsed属性,使之打开
* 然后为openedDiv添加collapsed属性(注意此时openedDiv一直是第一个菜单),
* 最后导致第一个菜单再次被关闭,结果就是一直打不开第一个菜单
* 问题解决:
* (4)、所以,我们在关闭openedDiv时,应该要先判断openedDiv是否正是当前元素,如果不是当前元素,才能为openedDiv添加collapsed属性使之关闭
6. 添加动画效果
为统一设置动画效果,将addClass()换成toggleClass(),但此时的toggleClass不需要有移除功能。判断代码:

openDiv != parentDiv && !hasClass(openDiv,"collapsed")

在切换之前获取菜单的高度begin,在切换之后获取菜单的高度end,动画效果就是将菜单的高度从begin向end过渡。
(1)在切换之后将菜单高度重置为初始高度begin
(2)调用move()函数执行动画,将目标高度设置为end的高度
(3)动画执行完毕之后,之前设置的菜单高度已经不再使用,此时要在move()函数的回调函数中将菜单高度设为空串,使之应用样式表中的样式。
(4)将切换菜单的动画封装成一个切换菜单的函数以简化代码。创建toggleMenu()函数,参数是要切换的对象obj,再在需要执行切换时调用toggleMenu()函数。

实现

代码:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title>二级菜单</title>
		<style type="text/css">
			* {
				margin: 0;
				padding: 0;
				list-style-type: none;
			}
			
			a,img {
				border: 0;
				text-decoration: none;
			}
			
			body {
				font: 12px/180% Arial, Helvetica, sans-serif, "新宋体";
			}
		</style>

		<link rel="stylesheet" type="text/css" href="css/sdmenu.css" />
		<script type="text/javascript" src="../3.29/定时器四的函数.js"></script>
		<script type="text/javascript" src="类的操作.js"></script>
		<script type="text/javascript">
			window.onload=function(){
				//获取所有菜单
				var menuSpan=document.getElementsByClassName("menuSpan");
				//创建变量保存已经被打开的菜单,因为默认第一个菜单是打开的,所以变量的初始值为第一个菜单
				var openedDiv=menuSpan[0].parentNode;
				//遍历所有菜单
				for(var i=0;i<menuSpan.length;i++){
					/*
					 * 为每个菜单标题绑定单击响应函数
					 */
					menuSpan[i].onclick=function(){
						//获取当前菜单,当前菜单是菜单标题的父元素
						var parentDiv=this.parentNode;
						//切换菜单的显示状态
						toggleMenu(parentDiv);
						/*
						 * 打开一个菜单的的时候应该关闭其他菜单
						 * 思路:
						 * 1、在遍历所有菜单前创建openedDiv变量用于保存已经被打开的菜单,
						 * 2、且在打开一个菜单时为当前菜单移除collapsed属性,然后为openedDiv添加collapsed属性关闭已经打开的菜单
						 * 3、在打开当前菜单后将openedDiv修改为当前菜单,以便打开其他菜单时关闭当前菜单
						 * 存在的隐含问题:
						 * 4、因为openedDiv初始值是第一个菜单,当我们第一次点击第一个菜单时为关闭第一个菜单,此时添加了collapsed属性
						 * 5、与此同时,我们又为openedDiv重新赋值为第一个菜单
						 * 6、所以当先关闭了第一个菜单,然后我们又想要打开第一个菜单时,此时第一个菜单为关闭状态,执行流程是:
						 * 先为第一个菜单移除collapsed属性,使之打开
						 * 然后为openedDiv添加collapsed属性(注意此时openedDiv一直是第一个菜单),
						 * 最后导致第一个菜单再次被关闭,结果就是一直打不开第一个菜单
						 * 问题解决:
						 * 7、所以,我们在关闭openedDiv时,应该要先判断openedDiv是否正是当前元素,如果不是当前元素,才能为openedDiv添加collapsed属性使之关闭
						 */
						
						//判断其他菜单是否是当前菜单
						if(openedDiv!=parentDiv && !hasClass(openedDiv,"collapsed")){
							//openedDiv不是当前菜单,此时要关闭已经打开的菜单,即为openedDiv添加collapsed属性
							//切换菜单的显示状态
							toggleMenu(openedDiv);
						}
						openedDiv=parentDiv;
						
						/*
						 * 设置菜单切换时的动画效果
						 * 思路:
						 * 1、为统一设置动画效果,我们需要将addClass方法替换成toggleClass方法,
						 * 但此时我们并不需要toggleClass的移除功能,所以需要先进行判断,
						 * 判断openedDiv是否含有collapsed属性,如果有collapsed属性,则不需要再添加;没有collapsed属性,则进入判断,为openedDiv添加该属性
						 * 2、在执行切换之前获取当前菜单的高度begin,再在执行切换之后获取菜单的高度end
						 * 3、在切换后,先将菜单的高度设置为begin,然后调用move()函数,将end设为目标高度,用以实现动画效果
						 * 4、动画执行完毕后,应该将之前设置的高度设为空串以使应用css样式表中的样式
						 */
					};
				}
				//将动画效果封装为一个函数以简化代码
				function toggleMenu(obj){
					//切换菜单之前,获取当前显示高度begin
					var begin=obj.offsetHeight;
					//点击按钮以后,为当前菜单切换collapsed属性,已经打开的关闭,已经关闭的打开
					toggleClass(obj,"collapsed");
					//切换菜单后,获取当前显示高度end
					var end=obj.offsetHeight;
					//先设置切换后的菜单高度为初始高度begin
					obj.style.height=begin+"px";
					//设置打开菜单的动画效果
					//再调用move()函数并将end设为目标高度以实现动画效果
					move(obj,"height",end,20,function(){
						obj.style.height="";
					});
				}
			};
		</script>
		
	</head>

	<body>

		<div id="my_menu" class="sdmenu">
			<div>
				<span class="menuSpan">在线工具</span>
				<a href="#">图像优化</a>
				<a href="#">收藏夹图标生成器</a>
				<a href="#">邮件</a>
				<a href="#">htaccess密码</a>
				<a href="#">梯度图像</a>
				<a href="#">按钮生成器</a>
			</div>
			<div class="collapsed">
				<span class="menuSpan">支持我们</span>
				<a href="#">推荐我们</a>
				<a href="#">链接我们</a>
				<a href="#">网络资源</a>
			</div>
			<div class="collapsed">
				<span class="menuSpan">合作伙伴</span>
				<a href="#">JavaScript工具包</a>
				<a href="#">CSS驱动</a>
				<a href="#">CodingForums</a>
				<a href="#">CSS例子</a>
			</div>
			<div class="collapsed">
				<span class="menuSpan">测试电流</span>
				<a href="#">Current or not</a>
				<a href="#">Current or not</a>
				<a href="#">Current or not</a>
				<a href="#">Current or not</a>
			</div>
		</div>
	</body>
</html>

(完)