函數節流實現滑動下拉菜單

涉及到的知識點(函數節流、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));
至此我們就成功的實現了滑動下拉菜單並解決其中遇到問題/撒花
源碼鏈接

Tags: