Instantly share code, notes, and snippets.
Created
May 23, 2021 12:48
-
Save iamrobert/0bbd137753c54d08ebefb41cbbec05f4 to your computer and use it in GitHub Desktop.
CodyHouse Mega Menu with level 1 click, and disable on mobile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// File#: _3_mega-site-navigation | |
// Usage: codyhouse.co/license | |
// iamrobert code to prevent click on touch | |
(function () { | |
var MegaNav = function (element) { | |
this.element = element; | |
this.search = this.element.getElementsByClassName('js-mega-nav__search'); | |
this.searchActiveController = false; | |
this.menu = this.element.getElementsByClassName('js-mega-nav__nav'); | |
this.menuItems = this.menu[0].getElementsByClassName('js-mega-nav__item'); | |
this.menuActiveController = false; | |
this.itemExpClass = 'mega-nav__item--expanded'; | |
this.classIconBtn = 'mega-nav__icon-btn--state-b'; | |
this.classSearchVisible = 'mega-nav__search--is-visible'; | |
this.classNavVisible = 'mega-nav__nav--is-visible'; | |
this.classMobileLayout = 'mega-nav--mobile'; | |
this.classDesktopLayout = 'mega-nav--desktop'; | |
this.layout = 'mobile'; | |
// store dropdown elements (if present) | |
this.dropdown = this.element.getElementsByClassName('js-dropdown'); | |
// expanded class - added to header when subnav is open | |
this.expandedClass = 'mega-nav--expanded'; | |
// check if subnav should open on hover | |
this.hover = this.element.getAttribute('data-hover') && this.element.getAttribute('data-hover') == 'on'; | |
initMegaNav(this); | |
}; | |
function initMegaNav(megaNav) { | |
setMegaNavLayout(megaNav); // switch between mobile/desktop layout | |
initSearch(megaNav); // controll search navigation | |
initMenu(megaNav); // control main menu nav - mobile only | |
initSubNav(megaNav); // toggle sub navigation visibility | |
iamrDisableClick(megaNav); // disable Click; | |
megaNav.element.addEventListener('update-menu-layout', function (event) { | |
setMegaNavLayout(megaNav); // window resize - update layout | |
}); | |
}; | |
function setMegaNavLayout(megaNav) { | |
var layout = getComputedStyle(megaNav.element, ':before').getPropertyValue('content').replace(/\'|"/g, ''); | |
if (layout == megaNav.layout) return; | |
megaNav.layout = layout; | |
Util.toggleClass(megaNav.element, megaNav.classDesktopLayout, megaNav.layout == 'desktop'); | |
Util.toggleClass(megaNav.element, megaNav.classMobileLayout, megaNav.layout != 'desktop'); | |
if (megaNav.layout == 'desktop') { | |
closeSubNav(megaNav, false); | |
// if the mega navigation has dropdown elements -> make sure they are in the right position (viewport awareness) | |
triggerDropdownPosition(megaNav); | |
} | |
closeSearch(megaNav, false); | |
resetMegaNavOffset(megaNav); // reset header offset top value | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
iamrDisableClick(megaNav); // disable Click; | |
}; | |
/* + REMOVE HOVER CLICK FOR MOBILE/TOUCH | |
======================================================================*/ | |
function disableLink() { | |
var menusLv1 = document.querySelectorAll('a.js-mega-nav__control'); | |
function preventDefaultListener(event) { | |
event.preventDefault(); | |
//event.stopPropagation(); | |
} | |
for (var i = 0, n = menusLv1.length; i < n; ++i) { | |
// menusLv1[i].classList.add('ol'); | |
menusLv1[i].setAttribute('data-href', menusLv1[i].getAttribute('href')); | |
menusLv1[i].href = ''; | |
menusLv1[i].setAttribute('aria-disabled', 'true'); | |
menusLv1[i].addEventListener('click', preventDefaultListener); | |
} | |
} | |
/* + ENABLE HOVER CLICK FOR DESKTOP/NON TOUCH | |
======================================================================*/ | |
function enableLink() { | |
var menusLv1 = document.querySelectorAll('a.js-mega-nav__control'); | |
for (var i = 0, n = menusLv1.length; i < n; ++i) { | |
// menusLv1[i].classList.remove('ol'); | |
if (menusLv1[i].getAttribute('data-href') !== null) { | |
menusLv1[i].href = menusLv1[i].getAttribute('data-href'); | |
} | |
menusLv1[i].removeAttribute('aria-disabled'); | |
} | |
} | |
/* + ENABLE/DISABLE ON SCREEN SIZE | |
======================================================================*/ | |
function iamrDisableClick(megaNav) { | |
// console.log(megaNav.layout); | |
//1. IF Mobile View | |
if (megaNav.layout == 'mobile') { | |
disableLink(); | |
} else { | |
enableLink(); | |
} | |
//2. If Touch Device | |
if ("ontouchstart" in document.documentElement) { | |
disableLink(); | |
} | |
//DISABLE ON TOUCH SWITCH (CHROME DEV) | |
document.addEventListener("DOMContentLoaded", initDetect) | |
function initDetect() { | |
// window.addEventListener("resize", detectDevice); | |
window.addEventListener("resize", throttle(detectDevice, 100)); | |
detectDevice(); | |
} | |
function detectDevice() { | |
var supportsTouch = !!navigator.maxTouchPoints || matchMedia('(hover: none), (pointer: coarse)').matches; | |
if (supportsTouch) { | |
disableLink(); | |
} | |
if (!supportsTouch && megaNav.layout == 'desktop') { | |
enableLink(); | |
} | |
} | |
} | |
function resetMegaNavOffset(megaNav) { | |
document.documentElement.style.setProperty('--mega-nav-offset-y', megaNav.element.getBoundingClientRect().top + 'px'); | |
}; | |
function closeNavigation(megaNav) { // triggered by Esc key press | |
// close search | |
closeSearch(megaNav); | |
// close nav | |
if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible)) { | |
toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, true); | |
} | |
//close subnav | |
closeSubNav(megaNav, false); | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
}; | |
function closeFocusNavigation(megaNav) { // triggered by Tab key pressed | |
// close search when focus is lost | |
if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible) && !document.activeElement.closest('.js-mega-nav__search')) { | |
toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, true); | |
} | |
// close nav when focus is lost | |
if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible) && !document.activeElement.closest('.js-mega-nav__nav')) { | |
toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, true); | |
} | |
// close subnav when focus is lost | |
for (var i = 0; i < megaNav.menuItems.length; i++) { | |
if (!Util.hasClass(megaNav.menuItems[i], megaNav.itemExpClass)) continue; | |
var parentItem = document.activeElement.closest('.js-mega-nav__item'); | |
if (parentItem && parentItem == megaNav.menuItems[i]) continue; | |
closeSingleSubnav(megaNav, i); | |
} | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
}; | |
function closeSearch(megaNav, bool) { | |
if (megaNav.search.length < 1) return; | |
if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible)) { | |
toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, bool); | |
} | |
}; | |
function initSearch(megaNav) { | |
if (megaNav.search.length == 0) return; | |
// toggle search | |
megaNav.searchToggles = document.querySelectorAll('[aria-controls="' + megaNav.search[0].getAttribute('id') + '"]'); | |
for (var i = 0; i < megaNav.searchToggles.length; i++) { | |
(function (i) { | |
megaNav.searchToggles[i].addEventListener('click', function (event) { | |
// toggle search | |
toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchToggles[i], true); | |
// close nav if it was open | |
if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible)) { | |
toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, false); | |
} | |
// close subnavigation if open | |
closeSubNav(megaNav, false); | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
}); | |
})(i); | |
} | |
}; | |
function initMenu(megaNav) { | |
if (megaNav.menu.length == 0) return; | |
// toggle nav | |
megaNav.menuToggles = document.querySelectorAll('[aria-controls="' + megaNav.menu[0].getAttribute('id') + '"]'); | |
for (var i = 0; i < megaNav.menuToggles.length; i++) { | |
(function (i) { | |
megaNav.menuToggles[i].addEventListener('click', function (event) { | |
// toggle nav | |
toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuToggles[i], true); | |
// close search if it was open | |
if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible)) { | |
toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, false); | |
} | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
}); | |
})(i); | |
} | |
}; | |
function toggleMenu(megaNav, element, controller, visibleClass, toggle, moveFocus) { | |
var menuIsVisible = Util.hasClass(element, visibleClass); | |
Util.toggleClass(element, visibleClass, !menuIsVisible); | |
Util.toggleClass(toggle, megaNav.classIconBtn, !menuIsVisible); | |
menuIsVisible ? toggle.removeAttribute('aria-expanded') : toggle.setAttribute('aria-expanded', 'true'); | |
if (menuIsVisible) { | |
if (toggle && moveFocus) toggle.focus(); | |
megaNav[controller] = false; | |
} else { | |
if (toggle) megaNav[controller] = toggle; | |
getFirstFocusable(element).focus(); // move focus to first focusable element | |
} | |
}; | |
function getFirstFocusable(element) { | |
var focusableEle = element.querySelectorAll('[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable], audio[controls], video[controls], summary'), | |
firstFocusable = false; | |
for (var i = 0; i < focusableEle.length; i++) { | |
if (focusableEle[i].offsetWidth || focusableEle[i].offsetHeight || focusableEle[i].getClientRects().length) { | |
firstFocusable = focusableEle[i]; | |
break; | |
} | |
} | |
return firstFocusable; | |
}; | |
function initSubNav(megaNav) { | |
// toggle subnavigation visibility | |
megaNav.element.addEventListener('click', function (event) { | |
if (megaNav.hover && megaNav.layout == 'desktop') return; // data-hover="on" | |
toggleSubNav(megaNav, event); | |
}); | |
if (megaNav.hover) { // data-hover="on" => use mouse events | |
megaNav.element.addEventListener('mouseover', function (event) { | |
if (megaNav.layout != 'desktop') return; | |
toggleSubNav(megaNav, event) | |
}); | |
megaNav.element.addEventListener('mouseout', function (event) { | |
if (megaNav.layout != 'desktop') return; | |
var mainItem = event.target.closest('.js-mega-nav__item'); | |
if (!mainItem) return; | |
var triggerBtn = mainItem.getElementsByClassName('js-mega-nav__control'); | |
if (triggerBtn.length < 1) return; | |
var itemExpanded = Util.hasClass(mainItem, megaNav.itemExpClass); | |
if (!itemExpanded) return; | |
var mainItemHover = event.relatedTarget; | |
if (mainItemHover && mainItem.contains(mainItemHover)) return; | |
Util.toggleClass(mainItem, megaNav.itemExpClass, !itemExpanded); | |
itemExpanded ? triggerBtn[0].removeAttribute('aria-expanded') : triggerBtn[0].setAttribute('aria-expanded', 'true'); | |
}); | |
} | |
}; | |
function toggleSubNav(megaNav, event) { | |
var triggerBtn = event.target.closest('.js-mega-nav__control'); | |
if (!triggerBtn) return; | |
var mainItem = triggerBtn.closest('.js-mega-nav__item'); | |
if (!mainItem) return; | |
var itemExpanded = Util.hasClass(mainItem, megaNav.itemExpClass); | |
if (megaNav.hover && itemExpanded && megaNav.layout == 'desktop') return; | |
Util.toggleClass(mainItem, megaNav.itemExpClass, !itemExpanded); | |
itemExpanded ? triggerBtn.removeAttribute('aria-expanded') : triggerBtn.setAttribute('aria-expanded', 'true'); | |
if (megaNav.layout == 'desktop' && !itemExpanded) closeSubNav(megaNav, mainItem); | |
// close search if open | |
closeSearch(megaNav, false); | |
resetNavAppearance(megaNav); // reset nav expanded appearance | |
}; | |
function closeSubNav(megaNav, selectedItem) { | |
// close subnav when a new sub nav element is open | |
if (megaNav.menuItems.length == 0) return; | |
for (var i = 0; i < megaNav.menuItems.length; i++) { | |
if (megaNav.menuItems[i] != selectedItem) closeSingleSubnav(megaNav, i); | |
} | |
}; | |
function closeSingleSubnav(megaNav, index) { | |
Util.removeClass(megaNav.menuItems[index], megaNav.itemExpClass); | |
var triggerBtn = megaNav.menuItems[index].getElementsByClassName('js-mega-nav__control'); | |
if (triggerBtn.length > 0) triggerBtn[0].removeAttribute('aria-expanded'); | |
}; | |
function triggerDropdownPosition(megaNav) { | |
// emit custom event to properly place dropdown elements - viewport awarness | |
if (megaNav.dropdown.length == 0) return; | |
for (var i = 0; i < megaNav.dropdown.length; i++) { | |
megaNav.dropdown[i].dispatchEvent(new CustomEvent('placeDropdown')); | |
} | |
}; | |
function resetNavAppearance(megaNav) { | |
((megaNav.element.getElementsByClassName(megaNav.itemExpClass).length > 0 && megaNav.layout == 'desktop') || megaNav.element.getElementsByClassName(megaNav.classSearchVisible).length > 0 || (megaNav.element.getElementsByClassName(megaNav.classNavVisible).length > 0 && megaNav.layout == 'mobile')) | |
? Util.addClass(megaNav.element, megaNav.expandedClass): Util.removeClass(megaNav.element, megaNav.expandedClass); | |
}; | |
//initialize the MegaNav objects | |
var megaNav = document.getElementsByClassName('js-mega-nav'); | |
if (megaNav.length > 0) { | |
var megaNavArray = []; | |
for (var i = 0; i < megaNav.length; i++) { | |
(function (i) { | |
megaNavArray.push(new MegaNav(megaNav[i])); | |
})(i); | |
} | |
// key events | |
window.addEventListener('keyup', function (event) { | |
if ((event.keyCode && event.keyCode == 27) || (event.key && event.key.toLowerCase() == 'escape')) { // listen for esc key events | |
for (var i = 0; i < megaNavArray.length; i++) { | |
(function (i) { | |
closeNavigation(megaNavArray[i]); | |
})(i); | |
} | |
} | |
// listen for tab key | |
if ((event.keyCode && event.keyCode == 9) || (event.key && event.key.toLowerCase() == 'tab')) { // close search or nav if it looses focus | |
for (var i = 0; i < megaNavArray.length; i++) { | |
(function (i) { | |
closeFocusNavigation(megaNavArray[i]); | |
})(i); | |
} | |
} | |
}); | |
window.addEventListener('click', function (event) { | |
if (!event.target.closest('.js-mega-nav')) closeNavigation(megaNavArray[0]); | |
}); | |
// resize - update menu layout | |
var resizingId = false, | |
customEvent = new CustomEvent('update-menu-layout'); | |
window.addEventListener('resize', function (event) { | |
clearTimeout(resizingId); | |
resizingId = setTimeout(doneResizing, 200); | |
}); | |
function doneResizing() { | |
for (var i = 0; i < megaNavArray.length; i++) { | |
(function (i) { | |
megaNavArray[i].element.dispatchEvent(customEvent) | |
})(i); | |
}; | |
}; | |
(window.requestAnimationFrame) // init mega site nav layout | |
? window.requestAnimationFrame(doneResizing): doneResizing(); | |
} | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment