Skip to content

Instantly share code, notes, and snippets.

@Dexdot
Created December 11, 2018 22:47
Show Gist options
  • Select an option

  • Save Dexdot/1b67f906507a64306f50b50a18734595 to your computer and use it in GitHub Desktop.

Select an option

Save Dexdot/1b67f906507a64306f50b50a18734595 to your computer and use it in GitHub Desktop.
multi-level dropdown menu
.dl-menuwrapper
// width: 100%
float: left
position: relative
perspective: 1000px
perspective-origin: 50% 200%
button
position: relative
ul
background: #f8f8f8
list-style: none
padding: 0
transform-style: preserve-3d
li
position: relative
li a
position: relative
display: block
padding: 16px
+link-color(#333)
.dl-menuwrapper
li
&.dl-back
> a
opacity: 0.5
&::after
content: ''
position: absolute
top: 50%
right: 16px
transform: translateY(-50%)
speak: none
width: 8px
height: 10px
background: url('../img/svg/chevron.svg') no-repeat center
background-size: cover
> a:not(:only-child)::after
content: ''
position: absolute
top: 50%
right: 16px
transform: translateY(-50%)
speak: none
width: 8px
height: 10px
background: url('../img/svg/chevron.svg') no-repeat center
background-size: cover
.dl-menu
position: absolute
width: 100%
opacity: 0
pointer-events: none
transform: translateY(8px)
backface-visibility: hidden
&.dl-menu-toggle
transition: all 0.3s ease
&.dl-menuopen
opacity: 1
pointer-events: auto
transform: translateY(0px)
li .dl-submenu
display: none
.dl-menu.dl-subview li
display: none
&.dl-subviewopen > a
display: none
&.dl-subview
display: block
> a
display: none
.dl-submenu
display: block
&.dl-subviewopen
display: block
> .dl-submenu
display: block
> li
display: block
.dl-menuwrapper
.dl-menu,
.dl-submenu
min-width: 265px
width: 265px
.dl-menuwrapper > .dl-submenu
position: absolute
width: 100%
top: 100%
left: 0
// Animation
.dl-menu.dl-animate-out-1
animation: MenuAnimOut1 0.4s
.dl-menu.dl-animate-in-1
animation: MenuAnimIn1 0.3s
.dl-menuwrapper > .dl-submenu.dl-animate-in-1
animation: SubMenuAnimIn1 0.4s ease
.dl-menuwrapper > .dl-submenu.dl-animate-out-1
animation: SubMenuAnimOut1 0.4s ease
@keyframes MenuAnimOut1
0%
50%
transform: translateZ(-250px) rotateY(30deg)
75%
transform: translateZ(-372.5px) rotateY(15deg)
opacity: 0.5
100%
transform: translateZ(-500px) rotateY(0deg)
opacity: 0
@keyframes MenuAnimIn1
0%
transform: translateZ(-500px) rotateY(0deg)
opacity: 0
20%
transform: translateZ(-250px) rotateY(30deg)
opacity: 0.5
100%
transform: translateZ(0px) rotateY(0deg)
opacity: 1
@keyframes SubMenuAnimIn1
0%
transform: translateX(50%)
opacity: 0
100%
transform: translateX(0px)
opacity: 1
@keyframes SubMenuAnimOut1
0%
transform: translateX(0%)
opacity: 1
100%
transform: translateX(50%)
opacity: 0
/* eslint-disable */
(function($, window, undefined) {
'use strict';
// global
var Modernizr = window.Modernizr,
$body = $('body');
$.DLMenu = function(options, element) {
this.$el = $(element);
this._init(options);
};
// the options
$.DLMenu.defaults = {
// classes for the animation effects
animationClasses: {
classin: 'dl-animate-in-1',
classout: 'dl-animate-out-1'
},
// callback: click a link that has a sub menu
// el is the link element (li); name is the level name
onLevelClick: function(el, name) {
return false;
},
// callback: click a link that does not have a sub menu
// el is the link element (li); ev is the event obj
onLinkClick: function(el, ev) {
return false;
}
};
$.DLMenu.prototype = {
_init: function(options) {
// options
this.options = $.extend(true, {}, $.DLMenu.defaults, options);
// cache some elements and initialize some variables
this._config();
var animEndEventNames = {
WebkitAnimation: 'webkitAnimationEnd',
OAnimation: 'oAnimationEnd',
msAnimation: 'MSAnimationEnd',
animation: 'animationend'
},
transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd',
msTransition: 'MSTransitionEnd',
transition: 'transitionend'
};
// animation end event name
this.animEndEventName =
animEndEventNames[Modernizr.prefixed('animation')] + '.dlmenu';
// transition end event name
(this.transEndEventName =
transEndEventNames[Modernizr.prefixed('transition')] + '.dlmenu'),
// support for css animations and css transitions
(this.supportAnimations = Modernizr.cssanimations),
(this.supportTransitions = Modernizr.csstransitions);
this._initEvents();
},
_config: function() {
this.open = false;
this.$trigger = this.$el.children('.dl-trigger');
this.$menu = this.$el.children('ul.dl-menu');
this.$menuitems = this.$menu.find('li:not(.dl-back)');
this.$el
.find('ul.dl-submenu')
.prepend('<li class="dl-back"><a href="#">Назад</a></li>');
this.$back = this.$menu.find('li.dl-back');
},
_initEvents: function() {
var self = this;
this.$trigger.on('click.dlmenu', function() {
if (self.open) {
self._closeMenu();
} else {
self._openMenu();
}
return false;
});
this.$menuitems.on('click.dlmenu', function(event) {
event.stopPropagation();
var $item = $(this),
$submenu = $item.children('ul.dl-submenu');
if ($submenu.length > 0) {
var $flyin = $submenu
.clone()
.css('opacity', 0)
.insertAfter(self.$menu),
onAnimationEndFn = function() {
self.$menu
.off(self.animEndEventName)
.removeClass(self.options.animationClasses.classout)
.addClass('dl-subview');
$item
.addClass('dl-subviewopen')
.parents('.dl-subviewopen:first')
.removeClass('dl-subviewopen')
.addClass('dl-subview');
$flyin.remove();
};
setTimeout(function() {
$flyin.addClass(self.options.animationClasses.classin);
self.$menu.addClass(self.options.animationClasses.classout);
if (self.supportAnimations) {
self.$menu.on(self.animEndEventName, onAnimationEndFn);
} else {
onAnimationEndFn.call();
}
self.options.onLevelClick($item, $item.children('a:first').text());
});
return false;
} else {
self.options.onLinkClick($item, event);
}
});
this.$back.on('click.dlmenu', function(event) {
var $this = $(this),
$submenu = $this.parents('ul.dl-submenu:first'),
$item = $submenu.parent(),
$flyin = $submenu.clone().insertAfter(self.$menu);
var onAnimationEndFn = function() {
self.$menu
.off(self.animEndEventName)
.removeClass(self.options.animationClasses.classin);
$flyin.remove();
};
setTimeout(function() {
$flyin.addClass(self.options.animationClasses.classout);
self.$menu.addClass(self.options.animationClasses.classin);
if (self.supportAnimations) {
self.$menu.on(self.animEndEventName, onAnimationEndFn);
} else {
onAnimationEndFn.call();
}
$item.removeClass('dl-subviewopen');
var $subview = $this.parents('.dl-subview:first');
if ($subview.is('li')) {
$subview.addClass('dl-subviewopen');
}
$subview.removeClass('dl-subview');
});
return false;
});
},
closeMenu: function() {
if (this.open) {
this._closeMenu();
}
},
_closeMenu: function() {
var self = this,
onTransitionEndFn = function() {
self.$menu.off(self.transEndEventName);
self._resetMenu();
};
this.$menu.removeClass('dl-menuopen');
this.$menu.addClass('dl-menu-toggle');
this.$trigger.removeClass('dl-active');
if (this.supportTransitions) {
this.$menu.on(this.transEndEventName, onTransitionEndFn);
} else {
onTransitionEndFn.call();
}
this.open = false;
},
openMenu: function() {
if (!this.open) {
this._openMenu();
}
},
_openMenu: function() {
var self = this;
// clicking somewhere else makes the menu close
$body.off('click').on('click.dlmenu', function() {
self._closeMenu();
});
this.$menu
.addClass('dl-menuopen dl-menu-toggle')
.on(this.transEndEventName, function() {
$(this).removeClass('dl-menu-toggle');
});
this.$trigger.addClass('dl-active');
this.open = true;
},
// resets the menu to its original state (first level of options)
_resetMenu: function() {
this.$menu.removeClass('dl-subview');
this.$menuitems.removeClass('dl-subview dl-subviewopen');
}
};
var logError = function(message) {
if (window.console) {
window.console.error(message);
}
};
$.fn.dlmenu = function(options) {
if (typeof options === 'string') {
var args = Array.prototype.slice.call(arguments, 1);
this.each(function() {
var instance = $.data(this, 'dlmenu');
if (!instance) {
logError(
'cannot call methods on dlmenu prior to initialization; ' +
"attempted to call method '" +
options +
"'"
);
return;
}
if (!$.isFunction(instance[options]) || options.charAt(0) === '_') {
logError("no such method '" + options + "' for dlmenu instance");
return;
}
instance[options].apply(instance, args);
});
} else {
this.each(function() {
var instance = $.data(this, 'dlmenu');
if (instance) {
instance._init();
} else {
instance = $.data(this, 'dlmenu', new $.DLMenu(options, this));
}
});
}
return this;
};
})(jQuery, window);
<div class="dl-menuwrapper js-dl-menu">
<button class="dl-trigger">Косметология</button>
<ul class="dl-menu">
<li>
<a href="#">Лицо</a>
<ul class="dl-submenu">
<li><a href="#">Лицо 1</a></li>
<li><a href="#">Лицо 2</a></li>
<li><a href="#">Лицо 3</a></li>
</ul>
</li>
<li>
<a href="#">Тело</a>
<ul class="dl-submenu">
<li><a href="#">Тело 1</a></li>
<li><a href="#">Тело 2</a></li>
<li><a href="#">Тело 3</a></li>
</ul>
</li>
<li>
<a href="#">Косметические средства</a>
<ul class="dl-submenu">
<li><a href="#">Косметические средства 1</a></li>
<li><a href="#">Косметические средства 2</a></li>
<li><a href="#">Косметические средства 3</a></li>
</ul>
</li>
<li>
<a href="#">Результаты работ</a>
<ul class="dl-submenu">
<li><a href="#">Результаты работ 1</a></li>
<li><a href="#">Результаты работ 2</a></li>
<li><a href="#">Результаты работ 3</a></li>
</ul>
</li>
<li>
<a href="#">Цены на услуги</a>
<ul class="dl-submenu">
<li><a href="#">Цены на услуги 1</a></li>
<li><a href="#">Цены на услуги 2</a></li>
<li><a href="#">Цены на услуги 3</a></li>
</ul>
</li>
<li>
<a href="#">Вопрос-ответ</a>
<ul class="dl-submenu">
<li><a href="#">Вопрос-ответ 1</a></li>
<li><a href="#">Вопрос-ответ 2</a></li>
<li><a href="#">Вопрос-ответ 3</a></li>
</ul>
</li>
</ul>
</div>
<script src="js/modernizr.js"></script>
$('.js-dl-menu').each((i, el) => {
$(el).dlmenu();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment