成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

商城“智能”導(dǎo)航欄實(shí)踐

nifhlheimr / 2924人閱讀

摘要:細(xì)心的用戶可能會(huì)發(fā)現(xiàn),在或者等大型網(wǎng)站中,當(dāng)鼠標(biāo)在一級(jí)導(dǎo)航欄中垂直移動(dòng)時(shí),二級(jí)菜單可以無(wú)延遲的響應(yīng)展示。很顯然,用戶希望在選擇某一級(jí)菜單下的子菜單時(shí),想要以斜向最短路徑移動(dòng)鼠標(biāo),而其他掠過的一級(jí)菜單也并不會(huì)激活。

需求與目標(biāo)

在電商的大屏主頁(yè)上,一般都會(huì)有一個(gè)顯眼的品類導(dǎo)航欄,作為整個(gè)商城的重要分流入口,客戶體驗(yàn)就必須要做到自然、極致。細(xì)心的用戶可能會(huì)發(fā)現(xiàn),在jd.com或者tmall.com等大型網(wǎng)站中,當(dāng)鼠標(biāo)在一級(jí)導(dǎo)航欄中垂直移動(dòng)時(shí),二級(jí)菜單可以無(wú)延遲的響應(yīng)展示。神奇的是,當(dāng)用戶將鼠標(biāo)懸浮在某一級(jí)菜單,想去點(diǎn)擊對(duì)應(yīng)的二級(jí)菜單區(qū)域時(shí),即使這時(shí)鼠標(biāo)掠過其他一級(jí)菜單,也并沒有切換到其他二級(jí)菜單,似乎這樣的菜單欄很懂你,可以準(zhǔn)確預(yù)測(cè)到你的行為,高大上的叫法是基于用戶行為預(yù)測(cè)的切換技術(shù),我稱之為“智能”導(dǎo)航欄,效果如下。

在動(dòng)手實(shí)踐之前,我們?cè)賮砻鞔_一下目標(biāo)效果:

鼠標(biāo)正常切換一級(jí)菜單時(shí),二級(jí)菜單無(wú)延遲響應(yīng);

鼠標(biāo)快速移動(dòng)到二級(jí)子菜單時(shí),要求一級(jí)菜單無(wú)冗余切換;

知識(shí)準(zhǔn)備

先來把需要用到的知識(shí)點(diǎn)劃出來。如果完成這樣一個(gè)小的需求,還能把輻射出的知識(shí)點(diǎn)都搞清楚,做到查漏補(bǔ)缺,再把相同的技術(shù)衍生到其他的場(chǎng)景,舉一反三,那么這樣的實(shí)踐才是充分的、有價(jià)值的。

事件代理與事件委托;

mouseenter和mouseover的區(qū)別;

debounce(防抖)和throttle(節(jié)流);

用向量叉乘判斷點(diǎn)在三角形內(nèi);(本實(shí)踐中選擇算法4,用叉乘符號(hào)相同判斷)

如何高效判斷兩個(gè)數(shù)字符號(hào)異同;

h5語(yǔ)義化標(biāo)簽--dl dt dd標(biāo)簽元素的語(yǔ)法結(jié)構(gòu)與使用;

對(duì)于以上我梳理出來的的知識(shí)點(diǎn),其中第2、第5、第6點(diǎn)比較簡(jiǎn)單,幾句話就可以說清楚,其余三點(diǎn)拿出一條就可以端端正正的寫出一篇文章,所以我已把我私藏的優(yōu)質(zhì)鏈接附上,如果你對(duì)于某些點(diǎn)比較模糊,請(qǐng)點(diǎn)擊跳轉(zhuǎn)學(xué)習(xí)。

實(shí)踐講解

我會(huì)采用漸進(jìn)增強(qiáng)的方式來進(jìn)行講解,完整的示例代碼請(qǐng)進(jìn)codepen。

基礎(chǔ)實(shí)現(xiàn)

首先對(duì)于文檔結(jié)構(gòu),遵循語(yǔ)義化的原則,左側(cè)的一級(jí)菜單用ul li組合.

  • 一級(jí)導(dǎo)航1
  • 一級(jí)導(dǎo)航2
  • ···

右側(cè)的子菜單,用dl dt dd標(biāo)簽來表達(dá),因?yàn)樗麄冏畛S迷谝粋€(gè)標(biāo)題下有若干對(duì)應(yīng)列表項(xiàng)的菜單場(chǎng)景。如需進(jìn)一步了解請(qǐng)點(diǎn)擊。

接下來,添加js交互。通過鼠標(biāo)在左側(cè)不同li的懸浮,來激活顯示右側(cè)不同的.sub_content塊,其中通過一級(jí)菜單的data-id屬性與其id值作為鉤子來進(jìn)行聯(lián)動(dòng)。

這里我們遇到選擇綁定mouseenter還是mouseover事件,其二者的區(qū)別可概括為:


使用mouseover/mouseout時(shí),在鼠標(biāo)指針經(jīng)過綁定元素或者經(jīng)過任何其子元素時(shí),都會(huì)觸發(fā) mouseover 事件。如果鼠標(biāo)移動(dòng)到其子元素上,而沒有離開綁定元素,也會(huì)觸發(fā)綁定元素的mouseout事件;

使用mouseenter/mouseleave時(shí),只有在鼠標(biāo)指針經(jīng)過綁定元素時(shí)(不包括鼠標(biāo)指針經(jīng)過任何子元素),才會(huì)觸發(fā)

mouseenter 事件。如果鼠標(biāo)沒有離開綁定元素,在其子元素上任意移動(dòng),也不會(huì)觸發(fā)mouseleave事件;

為了助于理解,我做了一個(gè)示例,請(qǐng)參考mouseenter/mouseover。

通過比較,顯然我們只需要給各li綁定mouseenter/mouseout事件即可。

var sub = $("#sub"); // 子級(jí)菜單包裹層
var activeRow, //  已激活的一級(jí)菜單
    activeMenu; //  已激活的子級(jí)菜單
    
$("#wrap").on("mouseenter", function() {
    // 顯示子菜單
    sub.removeClass("none");
})
.on("mouseleave", function() {
    // 隱藏子菜單
    sub.addClass("none");
    // 重置兩個(gè)已激活變量
    if (activeRow) {
        activeRow.removeClass("active");
        activeRow = null;
    }
    if (activeMenu) {
        activeMenu.addClass("none");
        activeMenu = null;
    }
})
.on("mouseenter", "li", function(e) {
    if (!activeRow) {
        activeRow = $(e.target).addClass("active");
    activeMenu = $("#" + activeRow.data("id"));
        activeMenu.removeClass("none");
        return;
    }
    // 若有已激活菜單,先還原之
    activeRow.removeClass("active");
    activeMenu.addClass("none");

    activeRow = $(e.target);
    activeRow.addClass("active");
    activeMenu = $("#" + activeRow.data("id"));
    activeMenu.removeClass("none");
});

以上便實(shí)現(xiàn)了基本效果,需要注意的是,在知識(shí)準(zhǔn)備一節(jié)中所提到的事件代理的運(yùn)用,是優(yōu)化DOM性能的一種很好的實(shí)踐,同時(shí)寫法又不失優(yōu)雅。

然而這個(gè)版本在體驗(yàn)上是有問題的,用戶為了選擇子菜單,必須要謹(jǐn)慎的讓鼠標(biāo)在當(dāng)前所選一級(jí)菜單的范圍內(nèi),以折線路徑移動(dòng)到子菜單,才可以進(jìn)一步選擇,如下圖。

很顯然,用戶希望在選擇某一級(jí)菜單下的子菜單時(shí),想要以斜向最短路徑移動(dòng)鼠標(biāo),而其他掠過的一級(jí)菜單也并不會(huì)激活。下面我們來對(duì)此做出改進(jìn)。

解決斜向移動(dòng)問題

當(dāng)鼠標(biāo)移動(dòng)時(shí),頻繁的觸發(fā)每一個(gè)一級(jí)菜單所綁定的mouseenter事件是問題的關(guān)鍵。因此我們很自然的想到延時(shí)觸發(fā),又為避免頻繁觸發(fā),引入防抖/節(jié)流。每次觸發(fā)一級(jí)菜單時(shí),并不讓他立即執(zhí)行展示子菜單的邏輯,而是延后300ms,直到最后一次觸發(fā)后300ms,判斷鼠標(biāo)的位置是否在子菜單區(qū)域內(nèi),如果在,便可直接return不做任何切換菜單操作,如下。

.on("mouseenter", "li", function(e) {
    if (!activeRow) {
        active(e.target);// 一個(gè)激活對(duì)應(yīng)子菜單的函數(shù)
        return;
    }
    if (timer) {
        clearTimeout(timer);
    }

    timer = setTimeout(function() {
        if (mouseInSub) {
            return;
        }
        activeRow.removeClass("active");
        activeMenu.addClass("none");
        active(e.target);

        timer = null;
    }, 300);
});

由此,因?yàn)槊恳淮吻袚Q一級(jí)菜單,都會(huì)有一個(gè)延遲300ms觸發(fā)的效果,所以當(dāng)用戶在一級(jí)菜單區(qū)域中上下移動(dòng)時(shí),或者真的想去快速切換菜單時(shí),這樣粗糙的延時(shí)處理在解決了斜向移動(dòng)的問題后,又引入了新的問題,如下圖。

那如何做到當(dāng)用戶真的想要快速切換一級(jí)菜單時(shí),子級(jí)菜單快速響應(yīng),而只有當(dāng)用戶想去選擇子級(jí)菜單時(shí),才會(huì)去運(yùn)用延時(shí)觸發(fā),進(jìn)而可以斜向移動(dòng)。至此,如果你的知識(shí)領(lǐng)域只局限于編程或者計(jì)算機(jī)科學(xué),那么要解決這個(gè)問題著實(shí)困難。這里我們需要些跨學(xué)科的啟發(fā)式思維,根據(jù)用戶行為抽象出一個(gè)數(shù)學(xué)模型,進(jìn)而實(shí)現(xiàn)對(duì)于用戶切換菜單的預(yù)測(cè)。

進(jìn)一步改善

事實(shí)上,我們可以根據(jù)用戶鼠標(biāo)的移動(dòng)軌跡抽象出這樣一個(gè)三角形(如下圖),構(gòu)成它的三個(gè)點(diǎn)分別是,子級(jí)菜單容器的左上頂點(diǎn)(top),及其左下頂點(diǎn)(bottom),另外一個(gè)是用戶鼠標(biāo)剛剛移動(dòng)經(jīng)過的點(diǎn)(pre)。處在三角形內(nèi)的cur點(diǎn)代表用戶鼠標(biāo)當(dāng)前的位置。其中pre和cur之間的距離取決于鼠標(biāo)移動(dòng)每次觸發(fā)mousemove事件的粒度,通常會(huì)很短很短,這里圖例為了方便觀察,做了合理放大。

這樣的一個(gè)三角形有何意義呢?在通常的用戶行為中,我們是否可以認(rèn)為當(dāng)鼠標(biāo)在三角形內(nèi)時(shí),便可以判定用戶有選擇子級(jí)菜單的傾向,當(dāng)鼠標(biāo)在三角形外時(shí),此時(shí)用戶更傾向于快速切換一級(jí)菜單。這樣在用戶不斷的移動(dòng)鼠標(biāo)時(shí),也同時(shí)會(huì)不斷的形成多個(gè)這樣的三角形,此時(shí),解決問題的突破口就轉(zhuǎn)化成,不斷監(jiān)聽鼠標(biāo)位置,并判斷當(dāng)前點(diǎn)是否在剛剛經(jīng)過的點(diǎn)和子級(jí)菜單左側(cè)上下兩頂點(diǎn)所形成的三角形中。

不斷監(jiān)聽鼠標(biāo)位置,我們可以通過mousemove輕松解決,只需要注意綁定和解綁的時(shí)機(jī),讓其只在菜單范圍內(nèi)觸發(fā),因?yàn)槌掷m(xù)的監(jiān)聽與觸發(fā)對(duì)于瀏覽器來講開銷不小。而判斷一個(gè)點(diǎn)是否在一個(gè)三角形內(nèi),這個(gè)問題需要用到知識(shí)準(zhǔn)備一節(jié)中的第四點(diǎn),我們選擇用向量叉乘符號(hào)相同來判斷一個(gè)點(diǎn)在一個(gè)三角形中。至于數(shù)學(xué)上的證明,不在本文討論范圍內(nèi),此處我們只需要知道該結(jié)論是嚴(yán)密的即可。

接下來我們用代碼來模擬實(shí)現(xiàn)向量及其叉乘:

// 向量是終點(diǎn)坐標(biāo)減去起點(diǎn)坐標(biāo)
function vector(a, b) {
    return {
        x: b.x - a.x,
        y: b.y - a.y
    }
}

// 向量的叉乘
function vectorPro(v1, v2) {
    return v1.x * v2.y - v1.y * v2.x;
}

然后我們利用上邊的兩個(gè)輔助函數(shù)來判斷一個(gè)點(diǎn)是否在某個(gè)三角形內(nèi),函數(shù)的入?yún)⑹撬膫€(gè)已知的點(diǎn),最終返回的結(jié)果是,所形成的三個(gè)向量叉乘后是否兩兩符號(hào)相同,相同即點(diǎn)在三角形內(nèi),反之亦反。

// 判斷點(diǎn)是否在三角形內(nèi)
function isPointInTranjgle(p, a, b, c) {
    var pa = vector(p, a);
    var pb = vector(p, b);
    var pc = vector(p, c);

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

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

// 用位運(yùn)算高效判斷符號(hào)相同
function sameSign(a, b) {
    return (a ^ b) >= 0;
}

這里需要留意sameSign這個(gè)用于判斷兩個(gè)值的符號(hào)是否相同的輔助函數(shù),判斷符號(hào)相同的方法有很多,但此處巧妙的利用了計(jì)算機(jī)二進(jìn)制的最高位--符號(hào)位。將兩個(gè)值按位異或,符號(hào)位不同取1,相同取0,所以如果最終符號(hào)位為1,即結(jié)果值整體小于0,則代表兩值符號(hào)不同,反之亦反。位運(yùn)算的執(zhí)行效率是要比我們直接操作非二進(jìn)制數(shù)的執(zhí)行效率高,所以應(yīng)用于此處大量頻繁地判斷符號(hào)異同的場(chǎng)景,對(duì)于性能優(yōu)化是很有幫助的。

最終,我們利用上邊準(zhǔn)備好的輔助函數(shù),通過跟蹤鼠標(biāo)的位置信息,判斷當(dāng)前是否需要啟用延時(shí)器,選擇性的實(shí)施上一節(jié)的優(yōu)化方案,這樣便實(shí)現(xiàn)了最終需求。(完整示例代碼codepen)

// 是否需要延遲
function needDelay(ele, curMouse, prevMouse) {
    if (!curMouse || !prevMouse) {
        return;
    }
    var offset = ele.offset();// offset() 方法返回或設(shè)置匹配元素相對(duì)于文檔的偏移(位置)

    // 左上點(diǎn)
    var topleft = {
        x: offset.left,
        y: offset.top
    };
    // 左下點(diǎn)
    var leftbottom = {
        x: offset.left,
        y: offset.top + ele.height()
    };

    return isPointInTranjgle(curMouse, prevMouse, topleft, leftbottom);
}
啟發(fā)

通過本例實(shí)踐,給我最深刻的體會(huì)便是,高數(shù)為提高生產(chǎn)力所帶來的價(jià)值,哈哈···

恕敝人淺薄,第一次看到這個(gè)實(shí)例時(shí)的那種激動(dòng)現(xiàn)在依然猶存,再加之前些天翻看了幾頁(yè)深度學(xué)習(xí)領(lǐng)域的一本經(jīng)典教材,有大半的篇幅講所用到的數(shù)學(xué)知識(shí),不禁感嘆數(shù)學(xué)原來是這么玩兒的,可惜了···

以碾壓式的高度和視野去看待問題,可以讓無(wú)解變有解,唯一解變多解,這才是我心目中的高手。

如果這篇文章可以讓你在coding本身、或者向量(數(shù)學(xué))對(duì)于其他類似場(chǎng)景(點(diǎn)線面)的應(yīng)用有所啟發(fā),甚至有對(duì)于教育引導(dǎo)方面的外延思考,我覺得我寫這篇文章的目的便達(dá)到了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90493.html

相關(guān)文章

  • 你們要的HTML布局技巧:如何規(guī)范搭建網(wǎng)頁(yè)架構(gòu)?

    摘要:主體內(nèi)容區(qū)域小米首頁(yè)下小米商城的主題內(nèi)容區(qū)域,也是整體網(wǎng)頁(yè)面積最廣的區(qū)塊實(shí)在不知道定主體內(nèi)容區(qū)塊時(shí)也可以根據(jù)面積比重來劃分,最大的那塊一定是主題中心,布局的重復(fù)性很高。 單就深入了解布局規(guī)范都足夠說上一個(gè)月的,今天我就不論大范圍,挑選小米網(wǎng)站首頁(yè)的部分區(qū)塊布局來講解吧! 下面是小米官網(wǎng)的首頁(yè),很多人一看到這樣的網(wǎng)頁(yè)就傻眼,不知道咋弄,要么就隨性布局,要么就干看著,其實(shí)遇到問題首先一點(diǎn)就...

    yvonne 評(píng)論0 收藏0
  • 你們要的HTML布局技巧:如何規(guī)范搭建網(wǎng)頁(yè)架構(gòu)?

    摘要:主體內(nèi)容區(qū)域小米首頁(yè)下小米商城的主題內(nèi)容區(qū)域,也是整體網(wǎng)頁(yè)面積最廣的區(qū)塊實(shí)在不知道定主體內(nèi)容區(qū)塊時(shí)也可以根據(jù)面積比重來劃分,最大的那塊一定是主題中心,布局的重復(fù)性很高。 單就深入了解布局規(guī)范都足夠說上一個(gè)月的,今天我就不論大范圍,挑選小米網(wǎng)站首頁(yè)的部分區(qū)塊布局來講解吧! 下面是小米官網(wǎng)的首頁(yè),很多人一看到這樣的網(wǎng)頁(yè)就傻眼,不知道咋弄,要么就隨性布局,要么就干看著,其實(shí)遇到問題首先一點(diǎn)就...

    KaltZK 評(píng)論0 收藏0
  • 網(wǎng)頁(yè)導(dǎo)航 html + css的代碼實(shí)現(xiàn)

    摘要:一般來講,我們的網(wǎng)頁(yè)導(dǎo)航欄是這么個(gè)模式來構(gòu)建在結(jié)構(gòu)上首先我們需要給導(dǎo)航欄的給個(gè)類名一般為然后就是一個(gè)無(wú)序表格由于導(dǎo)航欄的文字一般都是鏈接用來跳轉(zhuǎn)頁(yè)面要在里面包含一個(gè)首頁(yè)云云商城智慧門店?duì)I銷平臺(tái)媒體聯(lián)盟關(guān)于云道在樣式上目前我見過的分為兩種導(dǎo)航一般來講,我們的網(wǎng)頁(yè)導(dǎo)航欄是這么個(gè)模式來構(gòu)建在結(jié)構(gòu)上:1.首先我們需要給導(dǎo)航欄的div 給個(gè)類名 一般為nav2.然后就是一個(gè)無(wú)序表格?3.由于導(dǎo)航欄的文...

    keke 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<