函式節流實現滑動下拉選單

夕可死發表於2020-11-26

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

相關文章