Add files via upload
This commit is contained in:
parent
c4cf3e02aa
commit
960f9988b0
31 changed files with 1507 additions and 2 deletions
js
387
js/main.js
Normal file
387
js/main.js
Normal file
|
@ -0,0 +1,387 @@
|
|||
/**
|
||||
* 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
|
||||
// Each level is actually a different menu so 0 is root, 1 is sub-1, 2 sub-2, etc.
|
||||
this.current_menu = 0;
|
||||
|
||||
/* Determine what current menu actually is */
|
||||
var current_menu;
|
||||
this.menus.forEach(function(menuEl, pos) {
|
||||
var items = menuEl.querySelectorAll('.menu__item');
|
||||
items.forEach(function(itemEl, iPos) {
|
||||
var currentLink = itemEl.querySelector('.menu__link--current');
|
||||
if (currentLink) {
|
||||
// This is the actual menu__level that should have current
|
||||
current_menu = pos;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (current_menu) {
|
||||
this.current_menu = current_menu;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
this.breadCrumbs = false;
|
||||
var self = this;
|
||||
var submenus = [];
|
||||
|
||||
/* Loops over root level menu items */
|
||||
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_menu ) {
|
||||
classie.add(menuEl, 'menu__level--current');
|
||||
}
|
||||
|
||||
var menu_x = menuEl.getAttribute('data-menu');
|
||||
var links = menuEl.querySelectorAll('.menu__link');
|
||||
links.forEach(function(linkEl, lPos) {
|
||||
var submenu = linkEl.getAttribute('data-submenu');
|
||||
if (submenu) {
|
||||
var pushMe = {"menu":submenu, "name": linkEl.innerHTML };
|
||||
if (submenus[pos]) {
|
||||
submenus[pos].push(pushMe);
|
||||
} else {
|
||||
submenus[pos] = []
|
||||
submenus[pos].push(pushMe);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* For each MENU, find their parent MENU */
|
||||
this.menus.forEach(function(menuEl, pos) {
|
||||
var menu_x = menuEl.getAttribute('data-menu');
|
||||
submenus.forEach(function(subMenuEl, menu_root) {
|
||||
subMenuEl.forEach(function(subMenuItem, subPos) {
|
||||
if (subMenuItem.menu == menu_x) {
|
||||
self.menusArr[pos].backIdx = menu_root;
|
||||
self.menusArr[pos].name = subMenuItem.name;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// create breadcrumbs
|
||||
if( self.options.breadcrumbsCtrl ) {
|
||||
this.breadcrumbsCtrl = document.createElement('nav');
|
||||
this.breadcrumbsCtrl.className = 'menu__breadcrumbs';
|
||||
this.breadcrumbsCtrl.setAttribute('aria-label', 'You are here');
|
||||
this.el.insertBefore(this.breadcrumbsCtrl, this.el.firstChild);
|
||||
// add initial breadcrumb
|
||||
this._addBreadcrumb(0);
|
||||
|
||||
// Need to add breadcrumbs for all parents of current submenu
|
||||
if (self.menusArr[self.current_menu].backIdx != 0 && self.current_menu != 0) {
|
||||
this._crawlCrumbs(self.menusArr[self.current_menu].backIdx, self.menusArr);
|
||||
this.breadCrumbs = true;
|
||||
}
|
||||
|
||||
// Create current submenu breadcrumb
|
||||
if (self.current_menu != 0) {
|
||||
this._addBreadcrumb(self.current_menu);
|
||||
this.breadCrumbs = true;
|
||||
}
|
||||
}
|
||||
|
||||
// create back button
|
||||
if (this.options.backCtrl) {
|
||||
this.backCtrl = document.createElement('button');
|
||||
if (this.breadCrumbs) {
|
||||
this.backCtrl.className = 'menu__back';
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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_menu;
|
||||
// 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_menu].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_menu].menuEl,
|
||||
isBackNavigation = typeof clickPosition == 'undefined' ? true : false;
|
||||
|
||||
// slide out current menu items - first, set the delays for the items
|
||||
this.menusArr[this.current_menu].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_menu].menuEl,
|
||||
isBackNavigation = typeof clickPosition == 'undefined' ? true : false,
|
||||
// index of the nextMenuEl
|
||||
nextMenuIdx = this.menus.indexOf(nextMenuEl),
|
||||
|
||||
nextMenu = this.menusArr[nextMenuIdx],
|
||||
nextMenuEl = nextMenu.menuEl,
|
||||
nextMenuItems = nextMenu.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_menu = 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_menu === 0 && self.options.backCtrl ) {
|
||||
// hide back button
|
||||
classie.add(self.backCtrl, 'menu__back--hidden');
|
||||
}
|
||||
|
||||
// we can navigate again..
|
||||
self.isAnimating = false;
|
||||
|
||||
// focus retention
|
||||
nextMenuEl.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 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.href = '#'; // make it focusable
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MLMenu.prototype._crawlCrumbs = function(currentMenu, menuArray) {
|
||||
if (menuArray[currentMenu].backIdx != 0) {
|
||||
this._crawlCrumbs(menuArray[currentMenu].backIdx, menuArray);
|
||||
}
|
||||
// create breadcrumb
|
||||
this._addBreadcrumb(currentMenu);
|
||||
}
|
||||
|
||||
window.MLMenu = MLMenu;
|
||||
|
||||
})(window);
|
Loading…
Add table
Add a link
Reference in a new issue