Practice of "Smart" Navigation Bar in E-commerce
Published: 2017-12-16
Requirements and Goals
On the large homepage of an e-commerce site, there is usually a prominent category navigation bar that serves as an important diversion gateway for the whole mall. Customer experience must feel natural and exceptional. Observant users may notice that on major sites like jd.com or tmall.com, when the mouse moves vertically across the top-level navigation, the second-level menu responds and appears with no delay. Interestingly, when a user hovers over a top-level menu and intends to click a corresponding second-level area, even if the mouse briefly passes over other top-level items, the second-level menu does not switch to those other items. It’s as if the menu understands you and can predict your behavior. The sophisticated term is a switching technique based on user behavior prediction; I call it an “intelligent” navigation bar. The effect is as follows.

Before we start implementing, let’s clarify the target behavior:
When the mouse normally switches top-level menus, the second-level menu should respond with no delay;
When the mouse moves quickly toward a second-level submenu, the top-level menu should not switch redundantly;
Knowledge Preparation
First, let’s list the concepts we’ll need. If you can implement this small feature and also fully understand the related concepts, fill any knowledge gaps, and then apply the same techniques to other scenarios, that practice will be thorough and valuable.
The difference between mouseenter and mouseover;
Using vector cross product to determine whether a point lies inside a triangle; (In this practice I choose algorithm 4, using the cross product sign consistency check)
How to efficiently determine whether two numbers have the same or different signs;
H5 semantic tags – the syntax and usage of dl, dt, dd elements;
Among the points I listed above, items 2, 5, and 6 are relatively simple and can be explained in a few sentences; the other three points each merit a full article. I’ve attached my favorite quality links—if some points are still unclear to you, please click through to study them.
Practical Explanation
I will useprogressive enhancementas the approach for explanation. For the complete example code, please visitcodepen.
Basic Implementation
First, for the document structure, follow semantic principles. Useul li
combination for the left top-level menu.
<ul>
<li data-id="a">
<span> Top-level navigation 1 </span>
</li>
<li data-id="b">
<span> Top-level navigation 2 </span>
</li>
···
</ul>
For the right-side submenus, usedl dt dd
tags to express them, because they are most commonly used for menu scenarios where a heading has corresponding list items. For more detailsplease click.
<div id="sub" class="none">
<div id="a" class="sub_content none">
<dl>
<dt>
<a href="#"> Second-level menu 1 </a>
</dt>
<dd>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
</dd>
</dl>
<dl>
<dt>
<a href="#"> Second-level menu 1 </a>
</dt>
<dd>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
</dd>
</dl>
···
</div>
<div id="b" class="sub_content none">
<dl>
<dt>
<a href="#"> Second-level menu 2 </a>
</dt>
<dd>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
<a href="#"> Third-level menu </a>
</dd>
</dl>
···
</div>
···
</div>
Next, add the JS interactions. By hovering the mouse over different items on the leftli
we activate and display the corresponding content on the right.sub_content
blocks. Here, via the top-level menu’sdata-id
attribute and itsid
value as a hook, we link them together.
Here we need to choose whether to bindmouseenter
ormouseover
events. The difference between the two can be summarized as:
When using mouseover/mouseout, as the pointer moves over the bound element orover any of its child elements,the mouseover event will fire. If the mouse moves onto a child element without leaving the bound element, the bound element’s mouseout event will still fire;
When using mouseenter/mouseleave,onlywhen the mouse pointermoves over the bound element(excluding when the pointer moves over any child elements),will the mouseenter event fire. If the mouse hasn’t left the bound element, moving anywhere over its child elements won’t trigger the mouseleave event;
To aid understanding, I made an example. Please refer tomouseenter/mouseover.
By comparison, it’s clear we only need to bind eachli
withmouseenter
/mouseout
events.
var sub = $("#sub"); // wrapper for the sub-menu
var activeRow, // the activated top-level menu
activeMenu; // the activated sub-menu
$("#wrap").on("mouseenter", function() {
// show the sub-menu
sub.removeClass("none");
})
.on("mouseleave", function() {
// hide the sub-menu
sub.addClass("none");
// reset the two active variables
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;
}
// If there is an active menu, restore it first
activeRow.removeClass("active");
activeMenu.addClass("none");
activeRow = $(e.target);
activeRow.addClass("active");
activeMenu = $("#" + activeRow.data("id"));
activeMenu.removeClass("none");
});
The above implements the basic behavior. Note that the use ofevent delegationmentioned in the preparation section is a good practice for optimizing DOM performance, while keeping the code elegant.
However, this version has a UX problem: to select a submenu, users must carefully move the mouse within the bounds of the currently selected top-level menu and navigate along a bent path to the submenu before they can select it, as shown below.

Clearly, when users want to select a submenu under a top-level menu, they expect to move the mouse along the shortest diagonal path, and other top-level menus that are hovered over should not become active. Let's improve this.
Solving the diagonal movement problem
The core issue is that mouseenter events bound to each top-level menu are triggered frequently as the mouse moves. So we naturally consider delayed triggering, and to avoid excessive triggers we add debouncing/throttling. Instead of immediately showing the submenu on each trigger, we delay by 300ms. After the last trigger and a 300ms wait, we check whether the mouse is inside the submenu area; if it is, we simply return without switching menus, as shown below.
.on("mouseenter", "li", function(e) {
if (!activeRow) {
active(e.target); // a function that activates the corresponding submenu
return;
}
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
if (mouseInSub) {
return;
}
activeRow.removeClass("active");
activeMenu.addClass("none");
active(e.target);
timer = null;
}, 300);
});
Because each top-level menu switch now has a 300ms delayed effect, when users move the mouse up and down within the top-level menu area or truly want to switch menus quickly, this coarse delay—while solving the diagonal movement issue—introduces a new problem, as shown below.

How can we make submenus respond immediately when the user truly intends to switch top-level menus quickly, but apply delayed triggering only when the user intends to select a submenu so diagonal movement works? If your knowledge is limited to programming or computer science, solving this is difficult. Here we need somecross-disciplinary heuristic thinking: abstract a mathematical model from user behavior to predict when users will switch menus.
Further improvement
In fact, we can abstract a triangle from the user's mouse movement (shown below). Its three vertices are: the submenu container's top-left corner (top), its bottom-left corner (bottom), and the point the mouse just passed through (pre). The current mouse position (cur) lies inside the triangle. The distance between pre and cur depends on the granularity of mousemove events and is usually very small; the illustration is scaled up for clarity.

What does this triangle mean? In typical user behavior, we can assume that when the mouse is inside the triangle, the user is likely intending to select the submenu; when the mouse is outside the triangle, the user is more likely trying to quickly switch top-level menus. As the user moves the mouse, multiple such triangles form continuously. The breakthrough becomes:Continuously monitor the mouse position and determine whether the current point lies in the triangle formed by the last point the mouse passed and the submenu's left-side top and bottom vertices.
We can handle continuous mouse monitoring easily with mousemove, but be careful about when to bind and unbind it so it only runs within the menu area, since continuous listening is costly for the browser. To determine whether a point lies inside a triangle we use the fourth item from the preparation section: check whether the cross products of the vectors have the same sign. The mathematical proof is beyond this article; we only need to know the result is rigorous.
Next, we simulate vectors and their cross product in code:
// A vector is the endpoint coordinates minus the start point coordinates
function vector(a, b) {
return {
x: b.x - a.x,
y: b.y - a.y
}
}
// Cross product of two vectors
function vectorPro(v1, v2) {
return v1.x * v2.y - v1.y * v2.x;
}java
Then we use the two helper functions above to determine whether a point lies inside a given triangle. The function accepts four known points and returns whether the cross products of the three formed vectors have pairwise identical signs; if they do, the point is inside the triangle, otherwise it is not.
// Determine whether a point is inside a triangle
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);
}
// Use bitwise operations for an efficient sign comparison
function sameSign(a, b) {
return (a ^ b) >= 0;
}
Note something to watch heresameSign
This helper function is used to determine whether two values have the same sign. There are many ways to check sign equality, but here it cleverly uses the highest bit in a computer's binary representation — the sign bit.bitwise XOR, where differing sign bits yield 1 and matching bits yield 0. So if the final sign bit is 1 — i.e., the overall result is negative — the two values have different signs, and vice versa.Bitwise operations are more efficient to execute than operating directly on non-binary numbers, so applying them in scenarios that frequently and heavily check sign differences can be very helpful for performance optimization.
Finally, using the helper function prepared above and by tracking mouse positions, we determine whether to enable the delay, selectively apply the optimization from the previous section, and thereby meet the final requirement. (Complete example codecodepen)
// Whether to delay
function needDelay(ele, curMouse, prevMouse) {
if (!curMouse || !prevMouse) {
return;
}
var offset = ele.offset(); // offset() returns or sets the matched element's offset (position) relative to the document
// Top-left point
var topleft = {
x: offset.left,
y: offset.top
};
// Bottom-left point
var leftbottom = {
x: offset.left,
y: offset.top + ele.height()
};
return isPointInTranjgle(curMouse, prevMouse, topleft, leftbottom);
}
Insight
From practicing this example, my deepest takeaway is thatadvanced mathematics brings productivity value, haha...
Forgive my simple-mindedness — the excitement I felt when I first saw this example is still with me. A few days earlier I skimmed a classic deep learning textbook that devotes much of its pages to the mathematics involved, and I couldn't help but marvel at how mathematics can be used this way. What a pity...
Viewing problems from an overwhelmingly higher perspective and broader vision, can turn the unsolvable into solvable and a unique solution into multiple solutions — that, to me, defines a master.
If this article can inspire you in coding itself, or in applying vectors (mathematics) to similar scenarios (points, lines, planes), or even provoke thoughts about educational guidance, then I feel I have achieved my purpose in writing it.
Last updated