对于大型电子商务网站,不论是平台型电商还是垂直型电商,由于商品品类丰富,入口繁多,为方便用户快速定位及查询,在首页一般会挂出一个分类导航的菜单。例如国内的天猫,京东,当当,凡客,苏宁易购...国外的Amazon,Newegg等。
Like this:

对于上图呈现的菜单,常规的实现无外乎以下两种:
1,子菜单属于主菜单项的子级。Dom结构如下:

<ul><li><span>主菜单项1</span><div style="display:none;">子菜单1</div></li><li><span>主菜单项2</span><div style="display:none;">子菜单2</div></li>
</ul>

类似上述菜单的DOM结构,可以很方便的利用每个li的hover事件进行控制,实现父-子级的导航。不再赘述。

2,子菜单与主菜单项分离。Dom结构如下:

<ul><li><span>主菜单项1</span></li><li><span>主菜单项2</span></li>
</ul>
<div style="display:none;">子菜单1
</div>
<div style="display:none;">子菜单2
</div>

依然在每个li的hover事件中对子菜单的显隐进行控制,但由于子菜单不属于主菜单的子级,在li的 mouseout事件触发后,子菜单也会随之消失。这个时候常规的做法是利用延时。也就是在mouseout事件触发后,不立即隐藏子菜单。

上述两种情况实现的分类导航菜单,能满足绝大多数情况的使用需求。但就用户体验来说,依然有完善的空间。

具体说明请参考:
http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown

主要涉及两点,一是用户鼠标移动意图的判断,二是利用延时方式时,鼠标垂直移动菜单延时触发。

国内的天猫,国外的亚马逊和Newegg是已知的仅有几家对以上情况有所考虑的电商公司。同时天猫和亚马逊也分属国内和国外的行业老大。这也从一个侧面说明了技术实力和市场占有率是相辅相成的。

接下来我们对类似亚马逊分类导航菜单中,对用户鼠标移动意图判断进行一些代码层面的挖掘。主要参考了上文作者编写的插件进行一些说明。开源地址如下:
https://github.com/kamens/jQuery-menu-aim

如下图所示,主要运用斜率的比较判断4点处于三角区域123之内,从而识别用户的鼠标移动意图。

在代码方面:

1, 在document级别注册mousemove事件:

$(document).mousemove(mousemoveDocument);

2, 在mousemoveDocument事件处理函数中,将用户当前鼠标的移动坐标存储于mouseLocs数组中(数组仅存储三个值,动态更新)。

mouseLocs.push({ x: e.pageX, y: e.pageY });if (mouseLocs.length > MOUSE_LOCS_TRACKED) {mouseLocs.shift();
}

3, 注册主菜单每项的mouseenter事件:

$menu.mouseleave(mouseleaveMenu).find(options.rowSelector).mouseenter(mouseenterRow).mouseleave(mouseleaveRow).click(clickRow);

4, 在mouseenterRow事件处理函数中,会调用activationDelay 方法,利用斜率判断用户鼠标的移动意图:

  l  弹出数组的第一个和最后一个存储位。最后一位(loc)即当前鼠标点,第一位(prevLoc)即前一个鼠标点。

loc = mouseLocs[mouseLocs.length - 1],prevLoc = mouseLocs[0];

  

  l  求斜率。其中,decreasingCorner为upperRight点(示意图中的1点),increasingCorner为lowerRight点(示意图中的2点)。由此可见,所参考的三角区域是随着用户的鼠标移动动态更新的。

var decreasingSlope = slope(loc, decreasingCorner),increasingSlope = slope(loc, increasingCorner),prevDecreasingSlope = slope(prevLoc, decreasingCorner),prevIncreasingSlope = slope(prevLoc, increasingCorner);

  l  斜率公式为。(题外话:斜率好像是初中数学)

function slope(a, b) {return (b.y - a.y) / (b.x - a.x);};

  l  斜率比较,实现移动区域判断。如果当前点位于所参考的三角区域之类,则返回延迟值(默认为300ms),判定用户鼠标移动的目标为子菜单。

 if (decreasingSlope < prevDecreasingSlope &&increasingSlope > prevIncreasingSlope) {lastDelayLoc = loc;return DELAY;}

  

上述代码概述了亚马逊风格菜单鼠标触发区域判断实现的基本思路和流程。另外,我们会注意到代码中存储用户鼠标移动坐标的数组仅存储三个值(MOUSE_LOCS_TRACKED预设值为3)。大于三个值,会shift掉第一个值。

mouseLocs.push({ x: e.pageX, y: e.pageY });
if (mouseLocs.length > MOUSE_LOCS_TRACKED) {mouseLocs.shift();
}

在利用数组中存储的值进行三角区域判断的时候:

loc = mouseLocs[mouseLocs.length - 1],prevLoc = mouseLocs[0];

由上述代码可以看到,我们仅利用了首值和尾值。那么为什么会设定数组存储三个值?(或者存储更多值,修改MOUSE_LOCS_TRACKED即可)。

其实这里涉及到一个采样求趋势的概念。如图所示:

 

我们丢弃中间的3点,直接取1,2两点,会得到一个用户鼠标移动轨迹的一个更加平缓的过渡(用户鼠标移动的趋势)。即采样越宽泛,受个体元素干扰的可能越低。

如果没有这种类似缓冲的机制,直接取3点和2点进行判断,则会得出完全迥异的结论。(用户鼠标的移动还是比较容易产生“越界”的个体的。)

Okay,代码方面就分析到这里。下面插件贴出源码:

/*** menu-aim is a jQuery plugin for dropdown menus that can differentiate* between a user trying hover over a dropdown item vs trying to navigate into* a submenu's contents.** menu-aim assumes that you have are using a menu with submenus that expand* to the menu's right. It will fire events when the user's mouse enters a new* dropdown item *and* when that item is being intentionally hovered over.** __________________________* | Monkeys  >|   Gorilla  |* | Gorillas >|   Content  |* | Chimps   >|   Here     |* |___________|____________|** In the above example, "Gorillas" is selected and its submenu content is* being shown on the right. Imagine that the user's cursor is hovering over* "Gorillas." When they move their mouse into the "Gorilla Content" area, they* may briefly hover over "Chimps." This shouldn't close the "Gorilla Content"* area.** This problem is normally solved using timeouts and delays. menu-aim tries to* solve this by detecting the direction of the user's mouse movement. This can* make for quicker transitions when navigating up and down the menu. The* experience is hopefully similar to amazon.com/'s "Shop by Department"* dropdown.** Use like so:**      $("#menu").menuAim({*          activate: $.noop,  // fired on row activation*          deactivate: $.noop  // fired on row deactivation*      });**  ...to receive events when a menu's row has been purposefully (de)activated.** The following options can be passed to menuAim. All functions execute with* the relevant row's HTML element as the execution context ('this'):**      .menuAim({*          // Function to call when a row is purposefully activated. Use this*          // to show a submenu's content for the activated row.*          activate: function() {},**          // Function to call when a row is deactivated.*          deactivate: function() {},**          // Function to call when mouse enters a menu row. Entering a row*          // does not mean the row has been activated, as the user may be*          // mousing over to a submenu.*          enter: function() {},**          // Function to call when mouse exits a menu row.*          exit: function() {},**          // Selector for identifying which elements in the menu are rows*          // that can trigger the above events. Defaults to "> li".*          rowSelector: "> li",**          // You may have some menu rows that aren't submenus and therefore*          // shouldn't ever need to "activate." If so, filter submenu rows w/*          // this selector. Defaults to "*" (all elements).*          submenuSelector: "*",**          // Direction the submenu opens relative to the main menu. Can be*          // left, right, above, or below. Defaults to "right".*          submenuDirection: "right"*      });** https://github.com/kamens/jQuery-menu-aim* @mcmurphy fixed some bug in this plugin.
*/
(function ($) {$.fn.menuAim = function (opts) {//cache current menuSelector @mcmurphyopts.menuSelector = $(this).selector; // Initialize menu-aim for all elements in jQuery collectionthis.each(function () {init.call(this, opts);});return this;};function init(opts) {var $menu = $(this),activeRow = null,mouseLocs = [],lastDelayLoc = null,timeoutId = null,activeMenuSelector = null,   //current active submenu @mcmurphymenuSelector = opts.menuSelector, //cache row menu selector @mcmurphyleaveMenu = false,              //cache if the mouse leave the menu bound @mcmurphyoptions = $.extend({rowSelector: "> li",submenuSelector: "*",submenuDirection: "right",tolerance: 75,  // bigger = more forgivey when entering submenu
                enter: $.noop,exit: $.noop,activate: $.noop,deactivate: $.noop,exitMenu: $.noop}, opts);var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to trackDELAY = 300;  // ms delay when user appears to be entering submenu/*** Keep track of the last few locations of the mouse.*/var mousemoveDocument = function (e) {mouseLocs.push({ x: e.pageX, y: e.pageY });if (mouseLocs.length > MOUSE_LOCS_TRACKED) {mouseLocs.shift();}/*fix bug:when mouse move out the main-menu and the sub-menu area,hide the sub-menu@mcmurphy*/var rootMenuObj = $(e.target).closest(menuSelector);var rootSubmenuObj = $(e.target).closest(activeMenuSelector);if ((rootMenuObj.length === 0) && (rootSubmenuObj.length === 0)) {if (activeRow) {options.deactivate(activeRow); activeRow = null;leaveMenu = false;}}};/*** Cancel possible row activations when leaving the menu entirely*/var mouseleaveMenu = function () {if (timeoutId) {clearTimeout(timeoutId);}// If exitMenu is supplied and returns true, deactivate the// currently active row on menu exit.if (options.exitMenu(this)) {if (activeRow) {options.deactivate(activeRow); }activeRow = null;}};/*** Trigger a possible row activation whenever entering a new row.*/var mouseenterRow = function () {if (timeoutId) {// Cancel any previous activation delays
                clearTimeout(timeoutId);}leaveMenu = false;  options.enter(this);possiblyActivate(this);},mouseleaveRow = function () {options.exit(this);};/** Immediately activate a row if the user clicks on it.*/var clickRow = function () {activate(this);};/*** Activate a menu row.*/var activate = function (row) {if (row == activeRow || leaveMenu) {return;}if (activeRow) {options.deactivate(activeRow); }//cache the active submenu @mcmurphyactiveMenuSelector = options.activate(row); activeRow = row;};/*** Possibly activate a menu row. If mouse movement indicates that we* shouldn't activate yet because user may be trying to enter* a submenu's content, then delay and check again later.*/var possiblyActivate = function (row) {var delay = activationDelay();if (delay) {timeoutId = setTimeout(function () {possiblyActivate(row);}, delay);} else {activate(row); //catch the active submenu -- Km
            }};/*** Return the amount of time that should be used as a delay before the* currently hovered row is activated.** Returns 0 if the activation should happen immediately. Otherwise,* returns the number of milliseconds that should be delayed before* checking again to see if the row should be activated.*/var activationDelay = function () {if (!activeRow || !$(activeRow).is(options.submenuSelector)) {// If there is no other submenu row already active, then// go ahead and activate immediately.return 0;}var offset = $menu.offset(),upperLeft = {x: offset.left,y: offset.top - options.tolerance},upperRight = {x: offset.left + $menu.outerWidth(),y: upperLeft.y},lowerLeft = {x: offset.left,y: offset.top + $menu.outerHeight() + options.tolerance},lowerRight = {x: offset.left + $menu.outerWidth(),y: lowerLeft.y},loc = mouseLocs[mouseLocs.length - 1],prevLoc = mouseLocs[0];if (!loc) {return 0;}if (!prevLoc) {prevLoc = loc;}if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {// If the previous mouse location was outside of the entire// menu's bounds, immediately activate.leaveMenu = true; //@mcmurphyreturn 0;}if (lastDelayLoc &&loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {// If the mouse hasn't moved since the last time we checked// for activation status, immediately activate.return 0;}// Detect if the user is moving towards the currently activated// submenu.//
            // If the mouse is heading relatively clearly towards// the submenu's content, we should wait and give the user more// time before activating a new row. If the mouse is heading// elsewhere, we can immediately activate a new row.//
            // We detect this by calculating the slope formed between the// current mouse location and the upper/lower right points of// the menu. We do the same for the previous mouse location.// If the current mouse location's slopes are// increasing/decreasing appropriately compared to the// previous's, we know the user is moving toward the submenu.//
            // Note that since the y-axis increases as the cursor moves// down the screen, we are looking for the slope between the// cursor and the upper right corner to decrease over time, not// increase (somewhat counterintuitively).function slope(a, b) {return (b.y - a.y) / (b.x - a.x);};var decreasingCorner = upperRight,increasingCorner = lowerRight;// Our expectations for decreasing or increasing slope values// depends on which direction the submenu opens relative to the// main menu. By default, if the menu opens on the right, we// expect the slope between the cursor and the upper right// corner to decrease over time, as explained above. If the// submenu opens in a different direction, we change our slope// expectations.if (options.submenuDirection == "left") {decreasingCorner = lowerLeft;increasingCorner = upperLeft;} else if (options.submenuDirection == "below") {decreasingCorner = lowerRight;increasingCorner = lowerLeft;} else if (options.submenuDirection == "above") {decreasingCorner = upperLeft;increasingCorner = upperRight;}var decreasingSlope = slope(loc, decreasingCorner),increasingSlope = slope(loc, increasingCorner),prevDecreasingSlope = slope(prevLoc, decreasingCorner),prevIncreasingSlope = slope(prevLoc, increasingCorner);if (decreasingSlope < prevDecreasingSlope &&increasingSlope > prevIncreasingSlope) {// Mouse is moving from previous location towards the// currently activated submenu. Delay before activating a// new menu row, because user may be moving into submenu.lastDelayLoc = loc;return DELAY;}lastDelayLoc = null;return 0;};/*** Hook up initial menu events*/$menu.mouseleave(mouseleaveMenu).find(options.rowSelector).mouseenter(mouseenterRow).mouseleave(mouseleaveRow).click(clickRow);$(document).mousemove(mousemoveDocument);};
})(jQuery);

View Code

页面DOM结构:

<div class="mainNav"><ul><li></li><li></li></ul>
</div>
<div class="subNav" style="display: none;"></div>
<div class="subNav" style="display: none;"></div>

View Code

调用方式:

            $("div.mainNav").menuAim({activate: activateSubmenu,deactivate: deactivateSubmenu,rowSelector:">ul li"});//激活子菜单(示例)function activateSubmenu(row) {var $row = $(row),rowIndex = $("div.mainNav").find("li").index($row),submenuId = $("div.subNav").eq(rowIndex),$submenu = $(submenuId);if ($submenu.length>0) {$submenu.css({display: "block"});$row.addClass("hover");return submenuId;}}//隐藏子菜单(示例)function deactivateSubmenu(row) {var $row = $(row),rowIndex = $("div.mainNav").find("li").index($row),submenuId = $("div.subNav").eq(rowIndex);$(submenuId).css("display", "none");$row.removeClass("hover");return submenuId;}

View Code

感觉很久没有更新博客了。工作算不上忙,生活平缓的前进,真正的借口其实就是懒。一天一天,离自己最在乎的东西越远,越是失去了动力。小波说,人活着就是一个缓慢的被锤骟的过程。以前觉得没有谁锤得了我,这个家伙会一直生猛下去。现在看来,我就是一条慢慢萎缩下去的脉搏,终有一天会停止跳动。更可怕的你只能眼睁睁的看着这一过程的发生,什么都做不了,真他妈的可怕。

但说到底,这些都是一个读书人无病呻吟自怨自艾的状态。生活的本质是有时雄心万丈,有时忧愁惨淡。有时洒落阳光,有时沐浴阴凉。没事,该做什么做什么,站在路口别犹豫太久,选择一条路勇敢的走下去,前方什么都会有的~

转载于:https://www.cnblogs.com/mcmurphy/p/3338669.html

亚巴逊首页分类导航菜单触发区域控制原理窥视相关推荐

  1. jquery实现电商网站分类导航菜单

    一.HTML部分 <!DOCTYPE html> <html lang="zh"> <head> <meta charset=" ...

  2. jQuery 分类导航菜单条点击变色

    JQuery 分类导航菜单条点击变色,当点击导航菜单则当前点击选中导航菜单变色其它还原,依次类推. <script type="text/javascript" src=&q ...

  3. php左测导航栏,商城左侧大分类导航菜单教程完整代码

    提示:本页面右侧代码编辑器中的代码纯属展示调试代码 本代码最终的效果请用下面的的完整代码,复制到本地运行 完整代码html> 商城左侧大分类导航菜单 *{ margin:0; padding:0 ...

  4. Axure教程(中级):分类导航菜单高亮条的实现

    下面这种分类导航菜单效果,大家都见过,接下来将讲解此实现效果.主要是运用[绝对位置]移动效果和函数[[this.x]].[[Target.y]]来实现. 一.页面布局 从左侧拉入一个文本标签,文本为[ ...

  5. html仿写京东左侧,jQuery模仿京东/天猫商品左侧分类导航菜单效果

    现在天猫或者京东商品分类模块的默认的效果是这样的: 当鼠标滑过任意一栏导航分类时,就会出现相关详细分类模块,例如: 当鼠标移出蓝色框以外的区域,就会恢复默认的效果显示!然而使用jQuery的鼠标滑过事 ...

  6. 首页分类导航进一步优化

    首页进一步实现分类展示.分类导航,将所有的问题按照发布者的学校.学院和问题已解决还是为解决分类,因为问答数据库Qes-info设计的时候我们就只设计了问题内容.收藏的人数.发布者学校学院这些数据. d ...

  7. 【愚公系列】2022年11月 uniapp专题-优购电商首页-分类导航

    文章目录 前言 一.分类导航 1.获取分类导航的数据 2.完整源码 3.效果 前言 商品分类是指根据一定的管理目的,为满足商品生产.流通.消费活动的全部或部分需要,将管理范围内的商品集合总体,以所选择 ...

  8. php 京东首页分类导航,纯CSS京东商城分类导航菜单代码-懒人建站

    h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|r ...

  9. 安卓实现首页底部导航菜单中间图标凸起效果

    效果图 1,Activity代码如下: public class ThirdActivity extends BaseActivity {@BindView(R.id.fl_layout)FrameL ...

最新文章

  1. ACM 全部算法总结
  2. 死鱼——--摘自《项目百态:深入理解软件项目行为模式 》
  3. Unity中实现Hololens的三维空间映射Spatial mapping
  4. Spring MVC漏洞学习总结
  5. Linux内核社区是数字军火商、斯拉夫兵工厂甚至NSA的最爱
  6. Oracle Supplemental 补全日志介绍
  7. VTK:图表之ConstructTree
  8. php java c_当PHP、Java、C、C++ 这几种编程语言变成汽车是什么样的场景?
  9. redis 公网 安全_请务必注意 Redis 安全配置,否则将导致轻松被入侵
  10. 防治计算机病毒微格教案反思,数字化微格教学实验室设备常见故障及处理方法...
  11. qq邮箱 服务器认证失败怎么回事,为什么我的QQ邮箱登录不了 QQ邮箱无法登陆怎么解决...
  12. 机顶盒利旧改造,实现安卓和Linux双系统启动
  13. Android实现头像上传至数据库与保存 简易新闻(十七 上)
  14. windows android ios,如何将你的Android / iOS设备连接到Windows 10
  15. python是否被高估了?
  16. 基于对抗生成网络的图像转换技术【论文笔记】
  17. c程序设计语言布莱恩克尼汉,《C程序设计语言(第2版新版)典藏版》 —1.5.4 单词计数...
  18. 我从DuraznoConf中学到了编程的人性化方面
  19. 皮肤黑的人穿什么颜色的衣服比较好
  20. 未来五年最赚钱的不是股市、房地产,而是......

热门文章

  1. uniapp调用地图,进行位置查询,标记定位
  2. 动态修改ntp服务器,修改时区和建立ntp服务器
  3. 使用echarts实现简单的疾病知识图谱
  4. Android与IOS加固的五种方式
  5. [记录] Linux 回收脚本 recycle
  6. Java之jsch远程下载
  7. Sql进阶-postgres-like、similar to
  8. 基于TOMCAT的网页地址栏图标设置
  9. 从Markov Process到Markov Decision Process
  10. 网易蜂巢简单学习笔记