Skip to content

Instantly share code, notes, and snippets.

@mtandre
Last active February 27, 2019 19:23
Show Gist options
  • Save mtandre/742eb74dd3750e878190 to your computer and use it in GitHub Desktop.
Save mtandre/742eb74dd3750e878190 to your computer and use it in GitHub Desktop.
"Amazon inspired" hover navigation menu with trajectory projection
(function () {
var inTriangle = false, link = false,
navigation = document.querySelector('.navigation--sections'),
allNavItems = document.querySelectorAll('.navigation--item'),
x0, y0, x1, x2, x3, y1, y2, y3, hoverDelay;
navigation.addEventListener('mouseenter', onmouseenter);
navigation.addEventListener('mouseleave', onmouseleave);
function isInsideTriangle() {
// barycentric coordinates
var b0 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1),
b1 = ((x2 - x0) * (y3 - y0) - (x3 - x0) * (y2 - y0)) / b0,
b2 = ((x3 - x0) * (y1 - y0) - (x1 - x0) * (y3 - y0)) / b0,
b3 = ((x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)) / b0;
return b1 > 0 && b2 > 0 && b3 > 0;
}
function getParent(elem, classname) {
for ( ; elem && elem !== document; elem = elem.parentNode ) {
if ( elem.classList.contains( classname ) ) {
return elem;
}
}
return false;
}
function isHover(e) {
return (e.parentElement.querySelector(':hover') === e);
}
function onmouseenter(event) {
document.addEventListener('mousemove', onmousemove);
}
function onmouseleave(event) {
document.removeEventListener('mousemove', onmousemove);
}
function onmousemove(event) {
// set current mouse coordinates
x0 = event.clientX;
y0 = event.clientY;
// get menu item
var newLink = getParent(event.target, 'navigation--item');
if (!newLink) {
link = null;
return;
}
// new link, create baseline triangle
if (!link) {
clearTimeout(hoverDelay);
hoverDelay = setTimeout( function() {
console.log('new link, create baseline triangle');
link = newLink;
// safety - confirm there is only one active item
for (i = 0; i < allNavItems.length; ++i) {
allNavItems[i].classList.remove('dropdown--visible');
}
// active dropdown
link.classList.add('dropdown--visible');
// get the next mouseover el: dropdown's dims and coords
var next = link.lastElementChild.getBoundingClientRect();
// set triangle’s top point
x1 = x0;
y1 = y0;
// set triangle’s right point
x2 = next.right; // next.left + (0.33 * next.width);
y2 = next.top;
// set triangle’s left point
x3 = next.left;
y3 = next.top;
}, 100); // the delay user must be hovering on first interaction
} else if (link !== newLink) {
// different link
console.log('different link');
if (isInsideTriangle()) {
// on the way to the same link, build narrower triangle, set timer
console.log('on the way to the same link, build narrower triangle');
// get the next mouseover el: dropdown's dims and coords
var next = link.lastElementChild.getBoundingClientRect();
// set triangle’s top point
x1 = x0;
y1 = y0;
// set triangle’s right point
x2 = next.right; // next.left + (0.33 * next.width);
y2 = next.top;
// set triangle’s left point
x3 = next.left;
y3 = next.top;
} else {
// different link, reset and create new triangle
console.log('different link, reset and create new triangle');
for (i = 0; i < allNavItems.length; ++i) {
allNavItems[i].classList.remove('dropdown--visible');
}
link = newLink;
// active dropdown
link.classList.add('dropdown--visible');
// get the next mouseover el: dropdown's dims and coords
var next = link.lastElementChild.getBoundingClientRect();
// set triangle’s top point
x1 = x0;
y1 = y0;
// set triangle’s right point
x2 = next.right; // next.left + (0.33 * next.width);
y2 = next.top;
// set triangle’s left point
x3 = next.left;
y3 = next.top;
}
clearTimeout(hoverDelay);
} else {
// made it to the same link, clear timer
console.log('made it to the same link');
clearTimeout(hoverDelay);
}
return;
}
function onmouseleave(event) {
// clear timers
console.log('mouseleave');
clearTimeout(hoverDelay);
// remove any hover classes
for (i = 0; i < allNavItems.length; ++i) {
allNavItems[i].classList.remove('dropdown--visible');
}
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment