电商菜单去抖技术(debounce技术)

目的: 实现下图的效果.

效果展示
京东.PNG
  • 基本思路
    左边创建一个列表
    右边创建响应的弹出框
    鼠标给到mouseenter事件对应的展示右边的弹出框
代码如下:

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>行业分类</span></a></li>
            <li data-id="item1"><a href="javascript:void(0);"><i class="icon"></i><span>工业机械</span></a></li>
            <li data-id="item2"><a href="javascript:void(0);"><i class="icon"></i><span>洗衣机</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>工业</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>电动车</span></a></li>
            <li data-id="item7"><a href="javascript:void(0);"><i class="icon"></i><span>洗衣机</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>
            <!--多选的时候下面的确定按钮-->
            <div class="confirm"> </div>
            <!--行业专家-->
            <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;
}

/*左侧菜单模块*/
#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
当我们想将鼠标移入到右侧菜单中,鼠标必须保持在当前的小块中,否则就会造成右侧的弹出框改变。用户在使用的时候经常会出现鼠标不小心移入了其他的模块,导致右边展示内容变化,得不到用户希望的结果。

  • 解决方法

我们利用setTimeout这个函数来给一个延迟,这里我给的是300毫秒。延迟判断鼠标的位置,这样即便用户移入到左侧菜单的其他模块中短时间内也不会影响右侧菜单的展示。

问题2
如果我们给到了延迟,延迟也会给我们造成一些问题,如果用户并不打算往右侧的菜单中移入,只是想简单的切换左侧菜单的选项,我们给出延迟函数就会造成右侧弹出框的延迟展示,严重影响用户的体验。

  • 解决办法

这里我们引出了去抖技术,这个也是目前电商网站普遍运用的技术之一。

去抖技术

什么是去抖技术?
我个人的理解就是通过对用户可能做的行为进行预测,合理运用setTimeout这个函数,进而做出不同的处理,最大程度的提高用户体验度。
下面我就拿我做的项目举例:
下面是我们需要展示给用户的菜单。


jd.PNG
用户行为判断:
  1. 用户可能要从当前菜单移入到具体的弹出菜单。
  2. 用户打算切换当前菜单。
  • 情况1分析
jd1.png

用户当前的鼠标是A点,如果用户希望切换到右侧菜单那么用户鼠标的移动轨迹必定会经过三角形ABC,也就是说鼠标的下一个点一定会在三角形内部。就如同上图,我们假设下一个点是P点。那么我们就可以说只要P在三角形ABC内部那么用户目的就是为了切换到右侧菜单。此时我们给用户延迟,避免切换菜单时候造成问题。

  • 情况2分析
    如果用户想切换左边的菜单选项,那么用户鼠标移入的方向主要是上下移动,绝对不会朝着右侧移动,所以我们可以判断如果P不在三角形ABC内部时候,那么用户目的是切换左侧选项。

所以说判断P点在不在三角形ABC内部就显得很关键。
我们这里用到了大学的判断方法,即通过向量判断,如果向量PA、向量PB的叉乘和向量PB、向量PC的叉乘以及向量PC、向量PA的叉乘最后的符号是否一致,如果符号一致那么说明P点在三角形内部。

下面是具体代码:

//向量
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内部
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);
}

//判断符号是否相同
function sameSign(a,b) {
    return (a ^ b) >= 0
}

最后把代码整合一下就是最终的JS代码

//这里运用了debounce技术来处理菜单的移入移出问题
$(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);
}

//判断符号是否相同
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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容