Skip to content

Instantly share code, notes, and snippets.

@evanmwillhite
Last active June 22, 2018 14:45
Show Gist options
  • Save evanmwillhite/d14d310dc8d0edad53159d6ceea8948e to your computer and use it in GitHub Desktop.
Save evanmwillhite/d14d310dc8d0edad53159d6ceea8948e to your computer and use it in GitHub Desktop.
Multi-level Responsive Menu with vanilla JS (IE10+). Children completely hidden on large screens.
(function () {
'use strict';
var mainMenuToggle = document.getElementById('main-menu-toggle');
var mainMenu = document.getElementById('main-menu');
var mainMenuList = document.getElementById('main-menu__list');
var mainMenuClose = document.getElementById('main-menu__close');
var mainMenuParent = document.querySelectorAll('.main-menu__item--with-child > a');
var mainMenuBack = document.querySelectorAll('.main-menu__back');
var resizeTimer;
// If Mobile, add active class (for transitions)
// Where el is the DOM element you'd like to test for visibility
function isHidden(el) {
return (el.offsetParent === null);
}
// Encapsulate resizing
function menuTransition() {
// Add and remove menu active (transition) class
if (!isHidden(mainMenuToggle)) {
mainMenu.classList.add('active');
}
else {
mainMenu.classList.remove('active');
}
}
// Run menuTransition() on resize
window.addEventListener('resize', function () {
// http://www.paulirish.com/2009/throttled-smartresize-jquery-event-handler/
clearTimeout(resizeTimer);
resizeTimer = setTimeout(menuTransition, 250);
});
// Run menuTransition() on load
menuTransition();
// Menu Button Toggle
mainMenuToggle.addEventListener('click', function (e) {
mainMenu.classList.toggle('is-visible');
e.preventDefault();
});
// Menu Close Toggle
mainMenuClose.addEventListener('click', function (e) {
mainMenu.classList.toggle('is-visible');
mainMenuList.className = ('main-menu__list');
e.preventDefault();
});
// Mobile Click Menu Transition
for (var i = 0; i < mainMenuParent.length; i++) {
mainMenuParent[i].addEventListener('click', function (e) {
var level = this.getAttribute('data-childLevel');
var parent = this.parentNode;
// Make parent active
if (level === '1') {
var openItems = document.querySelectorAll('.open');
if (openItems.length) {
openItems[0].classList.remove('open');
}
if (parent.classList) {
parent.classList.add('open');
}
else {
parent.className += ' ' + 'open';
}
}
// Reset open items
mainMenuList.className = ('main-menu__list');
// Add is-active-LEVEL class for each section
if (mainMenuList.classList) {
mainMenuList.classList.add('is-active-' + level);
}
else {
mainMenuList.className += ' ' + 'is-active-' + level;
}
e.preventDefault();
});
}
// Mobile Menu Back button
for (var r = 0; r < mainMenuBack.length; r++) {
mainMenuBack[r].addEventListener('click', function (e) {
var level = this.getAttribute('data-childLevel');
mainMenuList.className = ('main-menu__list');
// Add is-active-LEVEL class for each section
if (mainMenuList.classList) {
mainMenuList.classList.add('is-active-' + level);
}
else {
mainMenuList.className += ' ' + 'is-active-' + level;
}
e.preventDefault();
});
}
})();
$main-menu-bg: #f2f2f2;
// Mobile Menu Button
.main-menu-toggle {
// include SVG icon
border: none;
cursor: pointer;
display: inline-block;
font-size: 300%;
left: -2px;
outline: none;
padding: .7em;
position: absolute;
top: -60px;
z-index: 50;
&:hover {
opacity: .8;
}
@include large {
display: none;
}
}
// Menu styles
// Nav
.main-menu {
background-color: $main-menu-bg;
height: 100%;
left: 0;
overflow-y: auto;
position: fixed;
transform: translateX(-100%);
top:0;
width: 100%;
z-index: 100;
-webkit-overflow-scrolling: touch;
@include large {
background-color: transparent;
overflow: visible;
position: static;
transform: none;
}
&.active {
transition: all 0.5s;
@include large {
transition: none;
}
}
&.is-visible {
transform: translateX(0);
}
}
// UL
.main-menu__list {
height: 100%;
padding: 0;
margin: 0;
transition: all 0.35s;
// @TODO make this 3 level support infinite
&.is-active-1 {
transform: translateX(-100%);
@include medium {
transform: none;
}
.open {
height: 100%;
position: static;
z-index: 1;
ul {
display: block;
@include medium {
display: none;
}
}
}
.open a[data-childlevel="1"] + ul {
height: 100%;
& > li {
height: auto;
}
}
}
&.is-active-2 {
transform: translateX(-200%);
@include medium {
transform: none;
}
.open {
height: 100%;
position: static;
z-index: 1;
ul {
display: block;
@include medium {
display: none;
}
}
// iPhone 5S requires parent to be static even though child is fixed.
> ul > li {
position: static;
}
}
.open a[data-childlevel="2"] + ul {
height: 100%;
& > li {
height: auto;
}
}
}
}
// LI
.main-menu__item {
list-style: none;
@include large {
display: inline-block;
}
}
// Child Menu
.main-menu__list--child {
display: none;
left: 0;
position: fixed;
top: 0;
transform: translateX(100%);
width: 100%;
@include large {
display: none;
}
}
// LI with child
.main-menu__item--with-child {
> .main-menu__link {
&:after {
// include arrow icon for parent item
content: "";
height: 25px;
position: absolute;
right: 16%;
top: 18%;
width: 25px;
@include small-med {
right: 12%;
}
@include small-large {
right: 9%;
}
}
@include large {
&:after {
display: none;
}
}
}
}
// A
.main-menu__link,
.main-menu__link:link,
.main-menu__link:visited,
.main-menu__back {
background-color: transparent;
border: none;
border-bottom: 1px solid $gray-lighter;
color: $red;
cursor: pointer;
display: block;
@include font-size(1.1);
font-weight: 600;
padding: 1em;
position: relative;;
text-align: left;
text-decoration: none;
width: 85%;
@include large {
border: none;
color: $gray-dark;
display: inline-block;
@include font-size(.9rem);
padding: 0 1em;
}
&:hover {
background-color: $red;
color: $white;
@include large {
background-color: transparent;
color: $red;
}
}
}
// Back button specific
.main-menu__back {
color: $gray-lighter;
&:before {
// include arrow icon for back
background-size: 10.2em 8.6em;
left: -.5em;
margin-right: 0;
top: 0.05em;
}
&:hover {
background-color: $white;
color: $gray-lighter;
}
}
// Mobile Menu Close Button
.main-menu__close {
background-color: $gray-dark;
border: none;
color: $white;
cursor: pointer;
font: 2em arial;
height: 100%;
position: absolute;
right: 0%;
top: 0;
width: 15%;
@include small-med {
width: 13%;
}
@include small-large {
width: 9%;
}
&::after {
// include close icon
content: "";
font-size: 137%;
height: 1em;
left: 50%;
margin-left: -32px;
position: absolute;
top:.3em;
width: 1.1em;
}
&:hover {
background-color: $gray;
}
@include large {
display: none;
}
}
<button type="button" id="main-menu-toggle" class="main-menu-toggle"></button>
<nav id="main-menu" class="main-menu">
<ul class="main-menu__list" id="main-menu__list">
<li class="main-menu__item main-menu__item--with-child">
<a href="{{ menu_link_url }}" class="main-menu__link" data-childLevel="1">{{ menu_link_text|default('First Item') }}</a>
<ul class="main-menu__list main-menu__list--child">
<button class="main-menu__back" type="button" data-childLevel="0">Back</button>
<li class="main-menu__item main-menu__item--with-child">
<a href="{{ menu_link_url }}" class="main-menu__link" data-childLevel="2">{{ menu_link_text|default('First Item - Child') }}</a>
<ul class="main-menu__list main-menu__list--child">
<button class="main-menu__back" type="button" data-childLevel="1">Back</button>
<li class="main-menu__item">
<a href="{{ menu_link_url }}" class="main-menu__link">{{ menu_link_text|default('First Item - 2nd Level') }}</a>
</li>
<li class="main-menu__item">
<a href="{{ menu_link_url }}" class="main-menu__link">{{ menu_link_text|default('First Item - 2nd Level(2)') }}</a>
</li>
<li class="main-menu__item">
<a href="{{ menu_link_url }}" class="main-menu__link">{{ menu_link_text|default('First Item - 2nd Level(3)') }}</a>
</li>
</ul>
</li>
<li class="main-menu__item">
<a href="{{ menu_link_url }}" class="main-menu__link">{{ menu_link_text|default('First Item - Child(2)') }}</a>
</li>
<li class="main-menu__item">
<a href="{{ menu_link_url }}" class="main-menu__link">{{ menu_link_text|default('First Item - Child(3)') }}</a>
</li>
</ul>
</li>
</ul>
<button type="button" id="main-menu__close" class="main-menu__close"></button>
</nav>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment