Multi-Level Menu

A simple multi-level menu with delayed item animations and an optional breadcrumb navigation and back button.
Blueprint_MutliLevelMenu

From our sponsor: Looking for an intuitive whiteboard style project management tool? Give Shortcut a try for free.

Today’s Blueprint is a simple menu with multiple levels. The idea is to animate each menu item once a level is changed. The animation starts with the item clicked and the delays are propagated through the neighbors. The animation delays follow the same logic for the incoming items of the new level of the multi-level menu. As optional elements we have a breadcrumb navigation and a back button (not shown in our demo). Deeper levels are referenced with a data attribute. We have added some example media queries for a mobile menu version with a menu toggle. We’ve also provided a simple callback example.

The icons used in the demo are from the Organic Food icon set by Wojciech Zasina and the Feather icon set by Cole Bemis.

Please note that we are using a couple of modern CSS properties, so only modern browsers are supported.

Browser Support:
  • ChromeSupported
  • FirefoxSupported
  • Internet ExplorerSupported from version 10+
  • SafariSupported
  • OperaSupported

The HTML

<!-- Main container -->
<div class="container">
	<!-- Menu toggle for mobile version -->
	<button class="action action--open" aria-label="Open Menu"><span class="icon icon--menu"></span></button>
	<!-- Menu -->
	<nav id="ml-menu" class="menu">
		<!-- Close button for mobile version -->
		<button class="action action--close" aria-label="Close Menu"><span class="icon icon--cross"></span></button>
		<div class="menu__wrap">
			<ul data-menu="main" class="menu__level">
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-1" href="#">Vegetables</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-2" href="#">Fruits</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-3" href="#">Grains</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-4" href="#">Mylk & Drinks</a></li>
			</ul>
			<!-- Submenu 1 -->
			<ul data-menu="submenu-1" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Stalk Vegetables</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Roots & Seeds</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Cabbages</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Salad Greens</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Mushrooms</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-1-1" href="#">Sale %</a></li>
			</ul>
			<!-- Submenu 1-1 -->
			<ul data-menu="submenu-1-1" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Fair Trade Roots</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Dried Veggies</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Our Brand</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Homemade</a></li>
			</ul>
			<!-- Submenu 2 -->
			<ul data-menu="submenu-2" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Citrus Fruits</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Berries</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-2-1" href="#">Special Selection</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Tropical Fruits</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Melons</a></li>
			</ul>
			<!-- Submenu 2-1 -->
			<ul data-menu="submenu-2-1" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Exotic Mixes</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Wild Pick</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Vitamin Boosters</a></li>
			</ul>
			<!-- Submenu 3 -->
			<ul data-menu="submenu-3" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Buckwheat</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Millet</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Quinoa</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Wild Rice</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Durum Wheat</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-3-1" href="#">Promo Packs</a></li>
			</ul>
			<!-- Submenu 3-1 -->
			<ul data-menu="submenu-3-1" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Starter Kit</a></li>
				<li class="menu__item"><a class="menu__link" href="#">The Essential 8</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Bolivian Secrets</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Flour Packs</a></li>
			</ul>
			<!-- Submenu 4 -->
			<ul data-menu="submenu-4" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Grain Mylks</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Seed Mylks</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Nut Mylks</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Nutri Drinks</a></li>
				<li class="menu__item"><a class="menu__link" data-submenu="submenu-4-1" href="#">Selection</a></li>
			</ul>
			<!-- Submenu 4-1 -->
			<ul data-menu="submenu-4-1" class="menu__level">
				<li class="menu__item"><a class="menu__link" href="#">Nut Mylk Packs</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Amino Acid Heaven</a></li>
				<li class="menu__item"><a class="menu__link" href="#">Allergy Free</a></li>
			</ul>
		</div>
	</nav>
	<div class="content">
		<p class="info">Please choose a category</p>
		<!-- Ajax loaded content here -->
	</div>
</div>
<!-- /view -->
<script src="js/classie.js"></script>
<script src="js/dummydata.js"></script>
<script src="js/main.js"></script>
<script>
(function() {
	var menuEl = document.getElementById('ml-menu'),
		mlmenu = new MLMenu(menuEl, {
			// breadcrumbsCtrl : true, // show breadcrumbs
			// initialBreadcrumb : 'all', // initial breadcrumb text
			backCtrl : false, // show back button
			// itemsDelayInterval : 60, // delay between each menu item sliding animation
			onItemClick: loadDummyData // callback: item that doesn´t have a submenu gets clicked - onItemClick([event], [inner HTML of the clicked item])
		});

	// mobile menu toggle
	var openMenuCtrl = document.querySelector('.action--open'),
		closeMenuCtrl = document.querySelector('.action--close');

	openMenuCtrl.addEventListener('click', openMenu);
	closeMenuCtrl.addEventListener('click', closeMenu);

	function openMenu() {
		classie.add(menuEl, 'menu--open');
	}

	function closeMenu() {
		classie.remove(menuEl, 'menu--open');
	}

	// simulate grid content loading
	var gridWrapper = document.querySelector('.content');

	function loadDummyData(ev, itemName) {
		ev.preventDefault();

		closeMenu();
		gridWrapper.innerHTML = '';
		classie.add(gridWrapper, 'content--loading');
		setTimeout(function() {
			classie.remove(gridWrapper, 'content--loading');
			gridWrapper.innerHTML = '<ul class="products">' + dummyData[itemName] + '<ul>';
		}, 700);
	}
})();
</script>

The CSS

/* Icons (made with Icomoon.io) */
/* Feather Icons by Cole Bemis */
@font-face {
	font-family: 'feather';
	font-weight: normal;
	font-style: normal;
	src: url('../fonts/feather/feather.eot?1gafuo');
	src: url('../fonts/feather/feather.eot?1gafuo#iefix') format('embedded-opentype'), url('../fonts/feather/feather.woff2?1gafuo') format('woff2'), url('../fonts/feather/feather.ttf?1gafuo') format('truetype'), url('../fonts/feather/feather.woff?1gafuo') format('woff'), url('../fonts/feather/feather.svg?1gafuo#feather') format('svg');
}

.icon {
	font-family: 'feather';
	font-weight: normal;
	font-style: normal;
	font-variant: normal;
	line-height: 1;
	text-transform: none;
	/* Better Font Rendering =========== */
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	speak: none;
}

.icon--arrow-left:before {
	content: 'e901';
}

.icon--menu:before {
	content: 'e903';
}

.icon--cross:before {
	content: 'e117';
} 

/* Menu styles */

.menu {
	position: fixed;
	top: 120px;
	left: 0;
	width: 300px;
	height: calc(100vh - 120px);
	background: #1c1d22;
}

.menu__wrap {
	position: absolute;
	top: 3.5em;
	bottom: 0;
	overflow: hidden;
	width: 100%;
}

.menu__level {
	position: absolute;
	top: 0;
	left: 0;
	visibility: hidden;
	overflow: hidden;
	overflow-y: scroll;
	width: calc(100% + 50px);
	height: 100%;
	margin: 0;
	padding: 0;
	list-style-type: none;
}

.menu__level--current {
	visibility: visible;
}

.menu__item {
	display: block;
	width: calc(100% - 50px);
}

.menu__link {
	font-weight: 600;
	position: relative;
	display: block;
	padding: 1em 2.5em 1em 1.5em;
	color: #bdbdbd;
	-webkit-transition: color 0.1s;
	transition: color 0.1s;
}

.menu__link[data-submenu]::after {
	content: 'e904';
	font-family: 'feather';
	position: absolute;
	right: 0;
	padding: 0.25em 1.25em;
	color: #2a2b30;
}

.menu__link:hover,
.menu__link[data-submenu]:hover::after {
	color: #5c5edc;
}

.menu__link--current::before {
	content: '0B7';
	font-size: 1.5em;
	line-height: 0;
	position: absolute;
	top: 50%;
	left: 0.5em;
	height: 4px;
	color: #5c5edc;
}

[class^='animate-'],
[class*=' animate-'] {
	visibility: visible;
}

.animate-outToRight .menu__item {
	-webkit-animation: outToRight 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
	animation: outToRight 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
}

@-webkit-keyframes outToRight {
	to {
		opacity: 0;
		-webkit-transform: translate3d(100%, 0, 0);
		transform: translate3d(100%, 0, 0);
	}
}

@keyframes outToRight {
	to {
		opacity: 0;
		-webkit-transform: translate3d(100%, 0, 0);
		transform: translate3d(100%, 0, 0);
	}
}

.animate-outToLeft .menu__item {
	-webkit-animation: outToLeft 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
	animation: outToLeft 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
}

@-webkit-keyframes outToLeft {
	to {
		opacity: 0;
		-webkit-transform: translate3d(-100%, 0, 0);
		transform: translate3d(-100%, 0, 0);
	}
}

@keyframes outToLeft {
	to {
		opacity: 0;
		-webkit-transform: translate3d(-100%, 0, 0);
		transform: translate3d(-100%, 0, 0);
	}
}

.animate-inFromLeft .menu__item {
	-webkit-animation: inFromLeft 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
	animation: inFromLeft 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
}

@-webkit-keyframes inFromLeft {
	from {
		opacity: 0;
		-webkit-transform: translate3d(-100%, 0, 0);
		transform: translate3d(-100%, 0, 0);
	}
	to {
		opacity: 1;
		-webkit-transform: translate3d(0, 0, 0);
		transform: translate3d(0, 0, 0);
	}
}

@keyframes inFromLeft {
	from {
		opacity: 0;
		-webkit-transform: translate3d(-100%, 0, 0);
		transform: translate3d(-100%, 0, 0);
	}
	to {
		opacity: 1;
		-webkit-transform: translate3d(0, 0, 0);
		transform: translate3d(0, 0, 0);
	}
}

.animate-inFromRight .menu__item {
	-webkit-animation: inFromRight 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
	animation: inFromRight 0.6s both cubic-bezier(0.7, 0, 0.3, 1);
}

@-webkit-keyframes inFromRight {
	from {
		opacity: 0;
		-webkit-transform: translate3d(100%, 0, 0);
		transform: translate3d(100%, 0, 0);
	}
	to {
		opacity: 1;
		-webkit-transform: translate3d(0, 0, 0);
		transform: translate3d(0, 0, 0);
	}
}

@keyframes inFromRight {
	from {
		opacity: 0;
		-webkit-transform: translate3d(100%, 0, 0);
		transform: translate3d(100%, 0, 0);
	}
	to {
		opacity: 1;
		-webkit-transform: translate3d(0, 0, 0);
		transform: translate3d(0, 0, 0);
	}
}

.menu__breadcrumbs {
	font-size: 0.65em;
	line-height: 1;
	position: relative;
	padding: 2.5em 3.75em 1.5em 2.5em;
}

.menu__breadcrumbs a {
	font-weight: bold;
	display: inline-block;
	cursor: pointer;
	vertical-align: middle;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: #5c5edc;
}

.menu__breadcrumbs a:last-child {
	pointer-events: none;
}

.menu__breadcrumbs a:hover {
	color: #8182e0;
}

.menu__breadcrumbs a:not(:last-child)::after {
	content: 'e902';
	font-family: 'feather';
	display: inline-block;
	padding: 0 0.5em;
	color: #33353e;
}

.menu__breadcrumbs a:not(:last-child):hover::after {
	color: #33353e;
}

.menu__back {
	font-size: 1.05em;
	position: absolute;
	z-index: 100;
	top: 0;
	right: 2.25em;
	margin: 0;
	padding: 1.365em 0.65em 0 0;
	cursor: pointer;
	color: #2a2b30;
	border: none;
	background: none;
}

.menu__back--hidden {
	pointer-events: none;
	opacity: 0;
}

.menu__back:hover,
.menu__back:focus {
	color: #fff;
	outline: none;
}

/* Open and close buttons */

.action {
	position: absolute;
	display: block;
	margin: 0;
	padding: 0;
	cursor: pointer;
	border: none;
	background: none;
}

.action:focus {
	outline: none;
}

.action--open {
	font-size: 1.5em;
	top: 1em;
	left: 1em;
	display: none;
	color: #fff;
	position: fixed;
	z-index: 1000;
}

.action--close {
	font-size: 1.1em;
	top: 1.25em;
	right: 1em;
	display: none;
	color: #45464e;
}

/* Example media query */
@media screen and (max-width: 40em) {
	.action--open,
	.action--close {
		display: block;
	}
	.menu {
		z-index: 1000;
		top: 0;
		width: 100%;
		height: 100vh;
		-webkit-transform: translate3d(-100%, 0, 0);
		transform: translate3d(-100%, 0, 0);
		-webkit-transition: -webkit-transform 0.3s;
		transition: transform 0.3s;
	}
	.menu--open {
		-webkit-transform: translate3d(0, 0, 0);
		transform: translate3d(0, 0, 0);
	}
}

The JavaScript

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2015, Codrops
 * http://www.codrops.com
 */
;(function(window) {

	'use strict';

	var support = { animations : Modernizr.cssanimations },
		animEndEventNames = { 'WebkitAnimation' : 'webkitAnimationEnd', 'OAnimation' : 'oAnimationEnd', 'msAnimation' : 'MSAnimationEnd', 'animation' : 'animationend' },
		animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ],
		onEndAnimation = function( el, callback ) {
			var onEndCallbackFn = function( ev ) {
				if( support.animations ) {
					if( ev.target != this ) return;
					this.removeEventListener( animEndEventName, onEndCallbackFn );
				}
				if( callback && typeof callback === 'function' ) { callback.call(); }
			};
			if( support.animations ) {
				el.addEventListener( animEndEventName, onEndCallbackFn );
			}
			else {
				onEndCallbackFn();
			}
		};

	function extend( a, b ) {
		for( var key in b ) { 
			if( b.hasOwnProperty( key ) ) {
				a[key] = b[key];
			}
		}
		return a;
	}

	function MLMenu(el, options) {
		this.el = el;
		this.options = extend( {}, this.options );
		extend( this.options, options );
		
		// the menus (<ul>´s)
		this.menus = [].slice.call(this.el.querySelectorAll('.menu__level'));
		// index of current menu
		this.current = 0;

		this._init();
	}

	MLMenu.prototype.options = {
		// show breadcrumbs
		breadcrumbsCtrl : true,
		// initial breadcrumb text
		initialBreadcrumb : 'all',
		// show back button
		backCtrl : true,
		// delay between each menu item sliding animation
		itemsDelayInterval : 60,
		// direction 
		direction : 'r2l',
		// callback: item that doesn´t have a submenu gets clicked
		// onItemClick([event], [inner HTML of the clicked item])
		onItemClick : function(ev, itemName) { return false; }
	};

	MLMenu.prototype._init = function() {
		// iterate the existing menus and create an array of menus, more specifically an array of objects where each one holds the info of each menu element and its menu items
		this.menusArr = [];
		var self = this;
		this.menus.forEach(function(menuEl, pos) {
			var menu = {menuEl : menuEl, menuItems : [].slice.call(menuEl.querySelectorAll('.menu__item'))};
			self.menusArr.push(menu);

			// set current menu class
			if( pos === self.current ) {
				classie.add(menuEl, 'menu__level--current');
			}
		});

		// create back button
		if( this.options.backCtrl ) {
			this.backCtrl = document.createElement('button');
			this.backCtrl.className = 'menu__back menu__back--hidden';
			this.backCtrl.setAttribute('aria-label', 'Go back');
			this.backCtrl.innerHTML = '<span class="icon icon--arrow-left"></span>';
			this.el.insertBefore(this.backCtrl, this.el.firstChild);
		}
		
		
		// create breadcrumbs
		if( self.options.breadcrumbsCtrl ) {
			this.breadcrumbsCtrl = document.createElement('nav');
			this.breadcrumbsCtrl.className = 'menu__breadcrumbs';
			this.el.insertBefore(this.breadcrumbsCtrl, this.el.firstChild);
			// add initial breadcrumb
			this._addBreadcrumb(0);
		}

		// event binding
		this._initEvents();
	};

	MLMenu.prototype._initEvents = function() {
		var self = this;

		for(var i = 0, len = this.menusArr.length; i < len; ++i) {
			this.menusArr[i].menuItems.forEach(function(item, pos) {
				item.querySelector('a').addEventListener('click', function(ev) { 
					var submenu = ev.target.getAttribute('data-submenu'),
						itemName = ev.target.innerHTML,
						subMenuEl = self.el.querySelector('ul[data-menu="' + submenu + '"]');

					// check if there's a sub menu for this item
					if( submenu && subMenuEl ) {
						ev.preventDefault();
						// open it
						self._openSubMenu(subMenuEl, pos, itemName);
					}
					else {
						// add class current
						var currentlink = self.el.querySelector('.menu__link--current');
						if( currentlink ) {
							classie.remove(self.el.querySelector('.menu__link--current'), 'menu__link--current');
						}
						classie.add(ev.target, 'menu__link--current');
						
						// callback
						self.options.onItemClick(ev, itemName);
					}
				});
			});
		}
		
		// back navigation
		if( this.options.backCtrl ) {
			this.backCtrl.addEventListener('click', function() {
				self._back();
			});
		}
	};

	MLMenu.prototype._openSubMenu = function(subMenuEl, clickPosition, subMenuName) {
		if( this.isAnimating ) {
			return false;
		}
		this.isAnimating = true;
		
		// save "parent" menu index for back navigation
		this.menusArr[this.menus.indexOf(subMenuEl)].backIdx = this.current;
		// save "parent" menu´s name
		this.menusArr[this.menus.indexOf(subMenuEl)].name = subMenuName;
		// current menu slides out
		this._menuOut(clickPosition);
		// next menu (submenu) slides in
		this._menuIn(subMenuEl, clickPosition);
	};

	MLMenu.prototype._back = function() {
		if( this.isAnimating ) {
			return false;
		}
		this.isAnimating = true;

		// current menu slides out
		this._menuOut();
		// next menu (previous menu) slides in
		var backMenu = this.menusArr[this.menusArr[this.current].backIdx].menuEl;
		this._menuIn(backMenu);

		// remove last breadcrumb
		if( this.options.breadcrumbsCtrl ) {
			this.breadcrumbsCtrl.removeChild(this.breadcrumbsCtrl.lastElementChild);
		}
	};

	MLMenu.prototype._menuOut = function(clickPosition) {
		// the current menu
		var self = this,
			currentMenu = this.menusArr[this.current].menuEl,
			isBackNavigation = typeof clickPosition == 'undefined' ? true : false;

		// slide out current menu items - first, set the delays for the items
		this.menusArr[this.current].menuItems.forEach(function(item, pos) {
			item.style.WebkitAnimationDelay = item.style.animationDelay = isBackNavigation ? parseInt(pos * self.options.itemsDelayInterval) + 'ms' : parseInt(Math.abs(clickPosition - pos) * self.options.itemsDelayInterval) + 'ms';
		});
		// animation class
		if( this.options.direction === 'r2l' ) {
			classie.add(currentMenu, !isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');
		}
		else {
			classie.add(currentMenu, isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');	
		}
	};

	MLMenu.prototype._menuIn = function(nextMenuEl, clickPosition) {
		var self = this,
			// the current menu
			currentMenu = this.menusArr[this.current].menuEl,
			isBackNavigation = typeof clickPosition == 'undefined' ? true : false,
			// index of the nextMenuEl
			nextMenuIdx = this.menus.indexOf(nextMenuEl),

			nextMenuItems = this.menusArr[nextMenuIdx].menuItems,
			nextMenuItemsTotal = nextMenuItems.length;

		// slide in next menu items - first, set the delays for the items
		nextMenuItems.forEach(function(item, pos) {
			item.style.WebkitAnimationDelay = item.style.animationDelay = isBackNavigation ? parseInt(pos * self.options.itemsDelayInterval) + 'ms' : parseInt(Math.abs(clickPosition - pos) * self.options.itemsDelayInterval) + 'ms';

			// we need to reset the classes once the last item animates in
			// the "last item" is the farthest from the clicked item
			// let's calculate the index of the farthest item
			var farthestIdx = clickPosition <= nextMenuItemsTotal/2 || isBackNavigation ? nextMenuItemsTotal - 1 : 0;

			if( pos === farthestIdx ) {
				onEndAnimation(item, function() {
					// reset classes
					if( self.options.direction === 'r2l' ) {
						classie.remove(currentMenu, !isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');
						classie.remove(nextMenuEl, !isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
					}
					else {
						classie.remove(currentMenu, isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');
						classie.remove(nextMenuEl, isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
					}
					classie.remove(currentMenu, 'menu__level--current');
					classie.add(nextMenuEl, 'menu__level--current');

					//reset current
					self.current = nextMenuIdx;

					// control back button and breadcrumbs navigation elements
					if( !isBackNavigation ) {
						// show back button
						if( self.options.backCtrl ) {
							classie.remove(self.backCtrl, 'menu__back--hidden');
						}
						
						// add breadcrumb
						self._addBreadcrumb(nextMenuIdx);
					}
					else if( self.current === 0 && self.options.backCtrl ) {
						// hide back button
						classie.add(self.backCtrl, 'menu__back--hidden');
					}

					// we can navigate again..
					self.isAnimating = false;
				});
			}
		});	
		
		// animation class
		if( this.options.direction === 'r2l' ) {
			classie.add(nextMenuEl, !isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
		}
		else {
			classie.add(nextMenuEl, isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
		}
	};

	MLMenu.prototype._addBreadcrumb = function(idx) {
		if( !this.options.breadcrumbsCtrl ) {
			return false;
		}

		var bc = document.createElement('a');
		bc.innerHTML = idx ? this.menusArr[idx].name : this.options.initialBreadcrumb;
		this.breadcrumbsCtrl.appendChild(bc);

		var self = this;
		bc.addEventListener('click', function(ev) {
			ev.preventDefault();

			// do nothing if this breadcrumb is the last one in the list of breadcrumbs
			if( !bc.nextSibling || self.isAnimating ) {
				return false;
			}
			self.isAnimating = true;
			
			// current menu slides out
			self._menuOut();
			// next menu slides in
			var nextMenu = self.menusArr[idx].menuEl;
			self._menuIn(nextMenu);

			// remove breadcrumbs that are ahead
			var siblingNode;
			while (siblingNode = bc.nextSibling) {
				self.breadcrumbsCtrl.removeChild(siblingNode);
			}
		});
	};

	window.MLMenu = MLMenu;

})(window);

Tagged with:

Mary Lou

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

http://www.codrops.com

Stay up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 70

Comments are closed.
  1. Very nice demo!

    Animation direction seems to be inverted though. Typically if you go into submenu, content is moved to the left side, not right. At least iOS follows this logic.

    • Superb demo! So slick! The sliding of content in from the left is better. From the right doesn’t feel as established.

    • Thanks for pointing that out 🙂 We’ve added a new option that allows to choose the direction. The default one opens to the left. Cheers, ML

  2. Beautiful!

    When the view port height enables scrolling on the menu, how did you get it to scroll without showing a scroll bar?

    Great stuff 🙂

  3. Very Nice … I Have Followed Your Articles For A Few Years Now And You Have Improved Web/APP Nav In My Opinion ,. Im Using A Few Of Your Concepts In My Project , Expand Overlay’s , Multi-Level Nav And Some Of Your Css Layouts .. Thanks For Your Beautiful Work.

  4. Can you integrate this with history push-state / pop-state?
    Would be great if it supported browser back / forward buttons!

    Also, would be nice if a page was loaded even when drilling down.

    For example if you click on Fruits, on the right all fruits would load.
    And then when you click on berries, it would only load the berries.
    Essentially asking if its possible to load a page on each click.

  5. Of course we all love your work, But there’s a big issue in this project, The sub menus are out of its (li)s!! Why is that? I try to use it in my project but i notice that i must change my framework so i used your old project Responsive Multi-Level Menu .. BUT .. I really want to try this awesome menu.

    Thanks for work .. Keep going…

    Regards, Hady

  6. How can I implement a anchor link at the last submenu? I have exactly this:

    Menu > submenu > submenu 2 > link to other page

    But when I click on the “link to other page” it just close the menu and do nothing.

  7. How can i load gallery of pictures of my own instead of dummy data? Do i insert my code on the dummydata.js instead of loading the icons in each line? Or is it somewhere else?

  8. When using this on an iPad (and presumably mobile device) when scrolling up and down the page it does not continue scrolling with momentum after you release your finger. How do you stop the code from preventing this?