電商菜單去抖技術(shù)(debounce技術(shù))

目的: 實(shí)現(xiàn)下圖的效果.

效果展示
京東.PNG
  • 基本思路
    左邊創(chuàng)建一個(gè)列表
    右邊創(chuàng)建響應(yīng)的彈出框
    鼠標(biāo)給到mouseenter事件對(duì)應(yīng)的展示右邊的彈出框
代碼如下:

HTML代碼:

<div class="section" id="news">
    <!--菜單-->
    <div class="menu">
        <ul>
            <li class="item_top" data-id="item"><a href="javascript:void(0);"><i class="icon"></i><span>行業(yè)分類</span></a></li>
            <li data-id="item1"><a href="javascript:void(0);"><i class="icon"></i><span>工業(yè)機(jī)械</span></a></li>
            <li data-id="item2"><a href="javascript:void(0);"><i class="icon"></i><span>洗衣機(jī)</span></a></li>
            <li data-id="item3"><a href="javascript:void(0);"><i class="icon"></i><span>家用電器</span></a></li>
            <li data-id="item4"><a href="javascript:void(0);"><i class="icon"></i><span>工業(yè)</span></a></li>
            <li data-id="item5"><a href="javascript:void(0);"><i class="icon"></i><span>化工</span></a></li>
            <li data-id="item6"><a href="javascript:void(0);"><i class="icon"></i><span>電動(dòng)車</span></a></li>
            <li data-id="item7"><a href="javascript:void(0);"><i class="icon"></i><span>洗衣機(jī)</span></a></li>
            <li data-id="item8"><a href="javascript:void(0);"><i class="icon"></i><span>工程</span></a></li>
            <li class="item_bottom" data-id="item"><a href="javascript:void(0);"><i class="icon"></i><span>定制</span></a></li>
        </ul>
        <!--彈出框-->
        <div class="menu_pop">
            <!--排序a-z-->
            <div class="industy_sort"></div>
            <a href=""><i class="icon multiple"></i></a>
            <!--模塊切換-->
            <div class="industy_part" id="part_item1">0</div>
            <div class="industy_part" id="part_item2">1</div>
            <div class="industy_part" id="part_item3">2</div>
            <div class="industy_part" id="part_item4">3</div>
            <div class="industy_part" id="part_item5">4</div>
            <div class="industy_part" id="part_item6">5</div>
            <div class="industy_part" id="part_item7">6</div>
            <div class="industy_part" id="part_item8">7</div>
            <!--多選的時(shí)候下面的確定按鈕-->
            <div class="confirm"> </div>
            <!--行業(yè)專家-->
            <div class="industy_people" id="people_item1">0</div>
            <div class="industy_people" id="people_item2">1</div>
            <div class="industy_people" id="people_item3">2</div>
            <div class="industy_people" id="people_item4">3</div>
            <div class="industy_people" id="people_item5">4</div>
            <div class="industy_people" id="people_item6">5</div>
            <div class="industy_people" id="people_item7">6</div>
            <div class="industy_people" id="people_item8">7</div>
        </div>
    </div>
    <div class="right_wrap">
        
    </div>
</div>

CSS代碼:

#news{
    background: #ffffff;
    width: 100%;
    max-width: 1024px;
    margin: auto;
    position: relative;
    z-index: 2;
    margin-top: -5px;
}

/*左側(cè)菜單模塊*/
#news>.menu{
    width: 180px;
    display: inline-block;
    box-sizing: border-box;
    position: relative;
    box-shadow: 2px 2px 6px 2px rgb(0,0,0);
    box-shadow: 2px 2px 6px 2px rgba(0,0,0,0.08);
    -webkit-box-shadow: 2px 2px 6px 2px rgba(0,0,0,0.08);
}

#news>.menu>ul{
    width: 100%;
}

#news>.menu>ul li{
    padding-left: 14px;
    line-height: 56px;
    height: 56px;
}

#news>.menu>ul li.active{
    background: #F03800;
}

#news>.menu>ul li.active a{
    text-decoration: none;
}

#news>.menu>ul li.active a span{
    color: #FFFFFF;
}

#news>.menu>ul li a{
    display: block;
    padding-left: 3px;
    border-bottom: 1px solid #F0F0F0;
    width: 100%;
}

#news>.menu>ul li a i {
    display: inline-block;
    width: 15px;
    height: 18px;
    vertical-align: middle;
    margin-right: 15px; 
    background: red;
}

#news>.menu li.item_top{
    line-height: 50px;
    height: 50px;
    
}

#news>.msg_menu>ul li a span{
    color: #666666;
    font-size: 18px;
    max-width: 130px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    vertical-align: middle;
}

#news>.menu>.menu_pop{
    width: 850px;
    height: 555px;
    background: #FFFFFF;
    position: absolute;
    left: 181px;
    top: 0px;
    z-index: 5;
    box-shadow: 0px 0px 12px 6px rgba(0,0,0,0.1);
    overflow: hidden;
    border-bottom-left-radius: 10px;
    border-bottom-right-radius: 10px;
    display: none;
}

/*#news>.menu>.menu_pop.active{*/
    /*display: block;*/
    /*visibility: visible;*/
/*}*/

#news>.menu>.menu_pop>.industy_part,
#news>.menu>.menu_pop>.industy_people{
    display: none;
}

#news>.menu>.menu_pop>.industy_part.active {
    display: block;
}

#news>.menu>.menu_pop>.industy_people.active {
    display: block;
}

問題1
當(dāng)我們想將鼠標(biāo)移入到右側(cè)菜單中,鼠標(biāo)必須保持在當(dāng)前的小塊中,否則就會(huì)造成右側(cè)的彈出框改變。用戶在使用的時(shí)候經(jīng)常會(huì)出現(xiàn)鼠標(biāo)不小心移入了其他的模塊,導(dǎo)致右邊展示內(nèi)容變化,得不到用戶希望的結(jié)果。

  • 解決方法

我們利用setTimeout這個(gè)函數(shù)來給一個(gè)延遲,這里我給的是300毫秒。延遲判斷鼠標(biāo)的位置,這樣即便用戶移入到左側(cè)菜單的其他模塊中短時(shí)間內(nèi)也不會(huì)影響右側(cè)菜單的展示。

問題2
如果我們給到了延遲,延遲也會(huì)給我們?cè)斐梢恍﹩栴},如果用戶并不打算往右側(cè)的菜單中移入,只是想簡(jiǎn)單的切換左側(cè)菜單的選項(xiàng),我們給出延遲函數(shù)就會(huì)造成右側(cè)彈出框的延遲展示,嚴(yán)重影響用戶的體驗(yàn)。

  • 解決辦法

這里我們引出了去抖技術(shù),這個(gè)也是目前電商網(wǎng)站普遍運(yùn)用的技術(shù)之一。

去抖技術(shù)

什么是去抖技術(shù)?
我個(gè)人的理解就是通過對(duì)用戶可能做的行為進(jìn)行預(yù)測(cè),合理運(yùn)用setTimeout這個(gè)函數(shù),進(jìn)而做出不同的處理,最大程度的提高用戶體驗(yàn)度。
下面我就拿我做的項(xiàng)目舉例:
下面是我們需要展示給用戶的菜單。


jd.PNG
用戶行為判斷:
  1. 用戶可能要從當(dāng)前菜單移入到具體的彈出菜單。
  2. 用戶打算切換當(dāng)前菜單。
  • 情況1分析
jd1.png

用戶當(dāng)前的鼠標(biāo)是A點(diǎn),如果用戶希望切換到右側(cè)菜單那么用戶鼠標(biāo)的移動(dòng)軌跡必定會(huì)經(jīng)過三角形ABC,也就是說鼠標(biāo)的下一個(gè)點(diǎn)一定會(huì)在三角形內(nèi)部。就如同上圖,我們假設(shè)下一個(gè)點(diǎn)是P點(diǎn)。那么我們就可以說只要P在三角形ABC內(nèi)部那么用戶目的就是為了切換到右側(cè)菜單。此時(shí)我們給用戶延遲,避免切換菜單時(shí)候造成問題。

  • 情況2分析
    如果用戶想切換左邊的菜單選項(xiàng),那么用戶鼠標(biāo)移入的方向主要是上下移動(dòng),絕對(duì)不會(huì)朝著右側(cè)移動(dòng),所以我們可以判斷如果P不在三角形ABC內(nèi)部時(shí)候,那么用戶目的是切換左側(cè)選項(xiàng)。

所以說判斷P點(diǎn)在不在三角形ABC內(nèi)部就顯得很關(guān)鍵。
我們這里用到了大學(xué)的判斷方法,即通過向量判斷,如果向量PA、向量PB的叉乘和向量PB、向量PC的叉乘以及向量PC、向量PA的叉乘最后的符號(hào)是否一致,如果符號(hào)一致那么說明P點(diǎn)在三角形內(nèi)部。

下面是具體代碼:

//向量
function vector(a,b) {
    return {
        x: b.x - a.x,
        y: b.y - a.y
    }
}
//向量的叉乘
function vectorProductor(v1,v2) {
    var res = v1.x*v2.y-v2.x*v1.y;
    return res;
}
//判斷P是否在三角形ABC內(nèi)部
function isPointInTrangle(p,a,b,c) {
    var pa = vector(p, a);
    var pb = vector(p, b);
    var pc = vector(p, c);

    var t1 = vectorProductor(pa, pb);
    var t2 = vectorProductor(pb, pc);
    var t3 = vectorProductor(pc, pa);

    return sameSign(t1, t2) && sameSign(t2, t3);
}

//判斷符號(hào)是否相同
function sameSign(a,b) {
    return (a ^ b) >= 0
}

最后把代碼整合一下就是最終的JS代碼

//這里運(yùn)用了debounce技術(shù)來處理菜單的移入移出問題
$(document).ready(function() {
    var activeRow,
        activeMenu,
        activeIndusty,
        timer,
        mouseInSub = false,
        mouseTrack = [],
        moveHandler;

    $(".menu_pop").mouseenter(function () {
        mouseInSub = true;
    }).mouseleave(function () {
        mouseInSub = false;
    });

    moveHandler = function(e) {
        mouseTrack.push({
            x: e.pageX,
            y: e.pageY
        })

        if (mouseTrack.length > 3) {
            mouseTrack.shift();
        }
    }


    $(".menu").mouseenter(function () {
        $(".menu_pop").show();
        $(document).bind('mousemove',moveHandler);
    }).mouseleave(function () {
        $(".menu_pop").hide();

        if (activeRow) {
            activeRow.removeClass('active');
            activeRow = null;
        }

        if (activeMenu) {
            activeMenu.hide();
            activeMenu = null;
        }

        if (activeIndusty) {
            activeIndusty.hide();
            activeIndusty = null;
        }

        $(document).unbind('mousemove',moveHandler);//解綁
    })

    $(".menu ul li").mouseenter(function () {
        if (!activeRow) {
            activeRow = $(this).addClass("active");
            activeMenu = $('#part_' + $(this).attr("data-id"));
            activeIndusty = $("#people_" + $(this).attr("data-id"));
            activeMenu.show();
            activeIndusty.show();
            return;
        }

        var that = $(this);

        if (timer) {
            clearTimeout(timer);
        }

        var currMousePos = mouseTrack[mouseTrack.length - 1];
        var leftCorner = mouseTrack[mouseTrack.length - 2];
        var delay = needDelay($(".menu_pop"),leftCorner,currMousePos);

        if (delay) {
            timer = setTimeout(function () {

                if (mouseInSub) {
                    return;//如果在子菜單中立即返回
                }

                activeRow.removeClass('active');
                activeMenu.hide();
                activeIndusty.hide();

                activeRow = that;
                activeRow.addClass('active');
                activeMenu = $('#part_' + that.attr("data-id"));
                activeMenu.show();
                activeIndusty = $("#people_" + that.attr("data-id"));
                activeIndusty.show();
            },300);
        } else {
            var preActiveRow = activeRow;
            var preActiveMenu = activeMenu;
            var preActiveIndusty = activeIndusty;

            activeRow = that;
            activeMenu = $('#part_' + that.attr("data-id"));
            activeIndusty = $("#people_" + that.attr("data-id"));

            preActiveRow.removeClass('active');
            preActiveMenu.hide();
            preActiveIndusty.hide();

            activeRow.addClass('active');
            activeMenu.show();
            activeIndusty.show();
        }

    });




});

//向量
function vector(a,b) {
    return {
        x: b.x - a.x,
        y: b.y - a.y
    }
}
//向量的叉乘
function vectorProductor(v1,v2) {
    var res = v1.x*v2.y-v2.x*v1.y;
    return res;
}

function isPointInTrangle(p,a,b,c) {
    var pa = vector(p, a);
    var pb = vector(p, b);
    var pc = vector(p, c);

    var t1 = vectorProductor(pa, pb);
    var t2 = vectorProductor(pb, pc);
    var t3 = vectorProductor(pc, pa);

    return sameSign(t1, t2) && sameSign(t2, t3);
}

//判斷符號(hào)是否相同
function sameSign(a,b) {
    return (a ^ b) >= 0
}

//是否需要延遲
function needDelay(elem,leftCorner,currMousePos) {
    var offset = elem.offset();
    var leftCorner = leftCorner;
    var currMousePos = currMousePos;

    var topLeft = {
        x: offset.left,
        y: offset.top
    }

    var bottomLeft = {
        x: offset.left,
        y: offset.top + elem.height()
    }

    return isPointInTrangle(currMousePos,leftCorner,topLeft,bottomLeft);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容