练习要求
实现二级菜单效果,即打开一个一级菜单时其他一级菜单不能同时打开,但可以同时关闭所有菜单,且要求菜单的打开和关闭有动画效果。(结合定时器和类的操作)
实现效果如图:
解决思路:
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>
(完)