函数节流实现滑动下拉菜单
涉及到的知识点(函数节流、this指向、事件冒泡、事件代理)
源码在文章底部
先把菜单最终效果图给大家搞上来
这里先把html和css代码贴上来
html部分
css部分
`
.main{
width: 260px;
margin: 0 auto;
margin-top: 50px;
}
.main ul{
list-style: none;
}
.main ul li {
position: relative;
border: 1px black solid;
line-height: 40px;
text-indent: 2em;
font-size: 16px;
}
.main ul>li:first-child {
border-top-right-radius: 10px;
border-top-left-radius: 10px;
}
.main ul>li:last-child{
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
.main ul li span {
display: block;
position: absolute;
right: 10px;
top: 5px;
height: 30px;
width: 30px;
background: url(./imgs/箭头.png) no-repeat center center;
background-size: contain;
}
.sub {
display: none;
background-color: #333;
overflow: hidden;
}
.main ul li .sub li {
box-sizing: border-box;
color: blanchedalmond;
border: 0px black solid;
border-bottom: 1px black solid;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.sub li:hover {
background-color: black;
}
.current {
transform: rotate(90deg);
}
`
li高度问题
其中有一个问题还是要重视一下的。
那就是一级菜单的高度不要用height设置否则二级菜单会覆盖别的菜单
如下
使用line-height撑开就好了
函数节流
函数节流是通过对定时器的操作保证一段时间内只执行一次事件触发的函数的技术,
思路:第一次触发函数时设置一个定时器,如果再次触发该事件时定时器的回调函数还未执行,
就不作任何回应,而当定时器执行完回调函数时将定时器清除,以保证下一次事件的正常触发。接下来我们写一下jq实现鼠标滑过弹出二级菜单的效果
`
let toggle = function(){
//children()选中指定子元素 | siblings()选中同级元素 | slideDown(时间)/slideUp(毫秒)滑动展开/闭合
$(this).children('span').addClass('current'); //给当前箭头添加.current使箭头变向
$(this).siblings().children('span').removeClass('current'); //把其他箭头的.curretn类名去掉
$sub = $(this).children('.sub'); //选中当前一级菜单
$sub.slideDown(500); //将当前一级菜单展开
$(this).siblings().children('.sub').slideUp(500) //将其他菜单闭合
}
`
但是我们把这个函数绑定到滑动事件上会出现这样的情况
我们可以看到即使鼠标不在滑动它还是在不断的触发事件对应的方法。
这是因为鼠标多次滑过这几个菜单导致大量的事件函数产生,而js要将这些函数全部执行一遍虽然很快,
但滑动效果的执行需要时间,导致这种令人头疼的现象出现。
而我们要做的是对事件触发进行节流
`
//节流
var throttle = function(func, delay) {
var timer = null; //定义一个定时器变量
return function() {
//保存当前执行上下文(对象环境),原因接下来会说
var context = this;
//如果没有设置定时器就设置一个,否则不作反馈
if (!timer) {
timer = setTimeout(function() {
func.apply(context); //把func(也就是toggle)绑定到之前保存的执行上下文中
//将定时器变量置空,以保证下一次事件的正常触发
timer = null;
}, delay);
}
}
}
$('ul>li').hover(throttle(toggle,100)); //建议设置40~60ms
`
this指向问题
众所周知
-普通/匿名函数中的this会指向调用者所在的对象(执行上下文)
-而箭头函数中的this则永久指向一开始定义时所在的对象(执行上下文)
-绑定的this指向会覆盖箭头函数中的this指向
那么我们就会发现在函数中因为要通过定时器的回调函数来执行toggle()函数,所以执行回调函数时其中的this会指向定时器setTimeout所在的window对象上
所以我们需要在执行回调函数之前用apply()方法将func绑定到了this指向的对象(也就是当前的li元素)
事件冒泡与代理
在我们解决了事件频繁触发的问题之后,我们又遇到了新的问题,那就是看起来并没有解决。
在这个案例中如果我们不通过父元素对子元素进行事件代理的话,会导致每个一级菜单触发的事件都是独立与另一个一级菜单的,而这样即使我们进行节流也没有意义因为事件源不统一,频繁触发的事件并不来自同一个地方。
所以我们需要通过对父元素(ul)来代理一级菜单的事件达到事件源的统一
$('ul').delegate('li','hover',throttle(toggle,50));
但当我们这样设置之后发现,菜单竟然不展开了。
发生这种情况的原因是hover()为非标准事件不可以进行事件代理。
那我们知道hover是对mouseenter和mouseleave的实现所以我们用其中一个也可以实现类似的效果
$('ul').delegate('li','mouseenter',throttle(toggle,50));
至此我们就成功的实现了滑动下拉菜单并解决其中遇到问题/撒花
源码链接