Выпадающее меню с подсказками

Приветствую вас!

У меня небольшая радость — в пятницу, 18 августа 2016 года при последующем апдейте Яндекса моему сайту zacompom.ru присвоили ТИц 10. Я понимаю что это очень небольшое событие, но ведь это только начало. Буду и впредь писать полезные статьи для вас.

И сегодня я бы хотел написать небольшую статью про меню, выпадающе и с подсказками. Кому интересно — устраивайтесь поудобнее, мы начинаем!

Это очень простое меню, где подменю при наведении появляются или выше или ниже основного меню, в зависимости от доступного пространства.

Ко мне часто поступают вопросы про хостинг, которым я пользуюсь и поэтому решил указать хостинг в статье https://sprinthost.ru. Вы можете попробовать попользоваться хостингом 30 дней бесплатно. Чтобы понять, как будет работать Ваш сайт на этом хостинге просто перенести свой сайт (в этом поможет поддержка хостинга бесплатно) и и таким образом сможете понять подходит хостинг Вам или нет. На этом хостинге находятся сайты с 20 000 тысяч посещаемость и сайты чувствуют себя отлично. Рекомендую! Да, если делать оплату на 1 год то получаете скидку 25%. И что мне нравится - тех. поддержка всегда помогает в технических вопросах, за что им спасибо. Как Вы понимаете не всегда проходит всё гладко и нет желания, чтобы сайт не был доступен тем самым страдал трафик и доход.

Подменю может появиться выше или ниже основного меню, в зависимости от того, где доступно больше места, как я и сказал выше. Будем использовать плагин Modernizr’s для обнаружения прикосновения, который будет реагировать на наведении мыши или на клик! Медиа-запросы показывают как настроить стили чтобы все отражалось нормально не только на больших, но и небольших экранах.

TooltipMenu

HTML код

<ul class="cbp-tm-menu">
	<li>
		<a href="#">Главная</a>
	</li>
	<li>
		<a href="#">Программирование</a>
		<ul class="zac_submenu">
			<li><a href="#" class="zac_icon_archive">Изучаем_1</a></li>
			<li><a href="#" class="zac_icon_cog">Изучаем_2</a></li>
			<li><a href="#" class="zac_icon_location">Изучаем_3</a></li>
			<li><a href="#" class="zac_icon_users">Изучаем_4</a></li>
			<li><a href="#" class="zac_icon_earth">Изучаем_5</a></li>
			<li><a href="#" class="zac_icon_location">Изучаем_6</a></li>
			<li><a href="#" class="zac_icon_mobile">Изучаем_7</a></li>
		</ul>
	</li>
	<li>
		<a href="#">Вордпресс</a>
		<ul class="zac_submenu">
			<li><a href="#" class="zac_icon_archive">Изучаем_1</a></li>
			<li><a href="#" class="zac_icon_cog">Изучаем_2</a></li>
			<li><a href="#" class="zac_icon_link">Изучаем_3</a></li>
			<li><a href="#" class="zac_icon_users">Изучаем_4</a></li>
			<li><a href="#" class="zac_icon_earth">Изучаем_5</a></li>
			<li><a href="#" class="zac_icon_location">Изучаем_6</a></li>
			<li><a href="#" class="zac_icon_mobile">Изучаем_7</a></li>
		</ul>
	</li>
	<li>
		<a href="#">Начинающим</a>
		<ul class="zac_submenu">
			<li><a href="#" class="zac_icon_screen">Изучаем_1</a></li>
			<li><a href="#" class="zac_icon_mail">Изучаем_2</a></li>
			<li><a href="#" class="zac_icon_contract">Изучаем_3</a></li>
			<li><a href="#" class="zac_icon_pencil">Изучаем_4</a></li>
			<li><a href="#" class="zac_icon_article">Изучаем_5</a></li>
			<li><a href="#" class="zac_icon_clock">Изучаем_6</a></li>
		</ul>
	</li>
</ul>

CSS код

/* подключаем шрифты-иконки */
@font-face {
	font-family: 'cbp-tmicons';
	src:url('../fonts/tmicons/cbp-tmicons.eot');
	src:url('../fonts/tmicons/cbp-tmicons.eot?#iefix') format('embedded-opentype'),
		url('../fonts/tmicons/cbp-tmicons.woff') format('woff'),
		url('../fonts/tmicons/cbp-tmicons.ttf') format('truetype'),
		url('../fonts/tmicons/cbp-tmicons.svg#cbp-tmicons') format('svg');
	font-weight: normal;
	font-style: normal;
}

/* сбрасываем стили для списков нашего меню */
.zac_menu,
.zac_menu ul {
	list-style: none;
}

/* зададим позиции для нашего меню */
.zac_menu {
	display: block;
	position: absolute;
	z-index: 1000;
	bottom: 0;
	width: 100%;
	background: #47a3da;
	text-align: right;
	padding: 0 2em;
	margin: 0;
	text-transform: capitalize;
}

/* начинается первый уровень нашего меню */
.zac_menu > li {
	display: inline-block;
	margin: 0 2.6em;
	position: relative;
}

.zac_menu > li > a {
	line-height: 4em;
	padding: 0 0.3em;
	font-size: 1.2em;
	display: block;
	color: #fff;
}

.no-touch .zac_menu > li > a:hover,
.no-touch .zac_menu > li > a:active {
	color: #02639d;
}

/* сами подменю а также переходы нашего меню */
.zac_submenu {
	position: absolute;
	display: block;
	visibility: hidden;
	opacity: 0;
	padding: 0;
	text-align: left;
	pointer-events: none;
	-webkit-transition: visibility 0s, opacity 0s;
	-moz-transition: visibility 0s, opacity 0s;
	transition: visibility 0s, opacity 0s;
}

.cbp-tm-show .zac_submenu {
	width: 16em;
	left: 50%;
	margin: 0 0 0 -8em;
	opacity: 1;
	visibility: visible;
	pointer-events: auto;
	-webkit-transition: visibility 0s, opacity 0.3s;
	-moz-transition: visibility 0s, opacity 0.3s;
	transition: visibility 0s, opacity 0.3s;
}

.cbp-tm-show-above .zac_submenu {
	bottom: 100%;
	padding-bottom: 10px;
}

.cbp-tm-show-below .zac_submenu {
	top: 100%;
	padding-top: 10px;
}

/* extreme cases: not enough space on the sides */
.cbp-tm-nospace-right .zac_submenu {
	right: 0;
	left: auto;
}

.cbp-tm-nospace-left .zac_submenu {
	left: 0;
}

/* last menu item has to fit on the screen */
.zac_menu > li:last-child .zac_submenu {
	right: 0;
}

/* тут мы укажем позицию для стрелки */

.zac_submenu:after {
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
}

.cbp-tm-show-above .zac_submenu:after {
	top: 100%;
	margin-top: -10px;
}

.cbp-tm-show-below .zac_submenu:after {
	bottom: 100%;
	margin-bottom: -10px;
}

.zac_submenu:after {
	border-color: transparent;
	border-width: 16px;
	margin-left: -16px;
	left: 50%;
}

.cbp-tm-show-above .zac_submenu:after {
	border-top-color: #fff;
}

.cbp-tm-show-below .zac_submenu:after {
	border-bottom-color: #fff;
}

.zac_submenu > li {
	display: block;
	background: #fff;
}

.zac_submenu > li > a {
	padding: 5px 2.3em 5px 0.6em; /* top/bottom paddings in 'em' cause a tiny "jump" in Chrome on Win */
	display: block;
	font-size: 1.2em;
	position: relative;
	color: #47a3da;
	border: 4px solid #fff;
	-webkit-transition: all 0.2s;
	-moz-transition: all 0.2s;
	transition: all 0.2s;
}

.no-touch .zac_submenu > li > a:hover,
.no-touch .zac_submenu > li > a:active {
	color: #fff;
	background: #47a3da;
}

/* иконки для всего меню */
.zac_submenu li a:before,
.zac_menu > li > a:before {
	font-family: 'cbp-tmicons';
	speak: none;
	font-style: normal;
	font-weight: normal;
	font-variant: normal;
	text-transform: none;
	line-height: 1;
	vertical-align: middle;
	margin-right: 0.6em;
	-webkit-font-smoothing: antialiased;
}

.zac_submenu li a:before {
	position: absolute;
	top: 50%;
	margin-top: -0.5em;
	right: 0.5em;
}

.zac_menu > li > a:not(:only-child):before {
	content: "\f0c9";
	font-size: 60%;
	opacity: 0.3;
}

.zac_icon_archive:before {
	content: "\e002";
}

.zac_icon_cog:before {
	content: "\e003";
}

.zac_icon_users:before {
	content: "\e004";
}

.zac_icon_earth:before {
	content: "\e005";
}

.zac_icon_location:before {
	content: "\e006";
}

.zac_icon_mobile:before {
	content: "\e007";
}

.zac_icon_screen:before {
	content: "\e008";
}

.zac_icon_mail:before {
	content: "\e009";
}

.zac_icon_contract:before {
	content: "\e00a";
}

.zac_icon_pencil:before {
	content: "\e00b";
}

.zac_icon_article:before {
	content: "\e00c";
}

.zac_icon_clock:before {
	content: "\e00d";
}

.zac_icon_videos:before {
	content: "\e00e";
}

.zac_icon_pictures:before {
	content: "\e00f";
}

.zac_icon_link:before {
	content: "\e010";
}

.zac_icon_refresh:before {
	content: "\e011";
}

.zac_icon_help:before {
	content: "\e012";
}

/* далее идут медиа-запросы для отображения на разных разрешениях экранов */
@media screen and (max-width: 55.6875em) {
	.zac_menu {
		font-size: 80%;
	}
}

@media screen and (max-height: 25.25em), screen and (max-width: 44.3125em) {

	.zac_menu {
		font-size: 100%;
		position: relative;
		text-align: center;
		padding: 0;
		top: auto;
	}

	.zac_menu > li {
		display: block;
		margin: 0;
		border-bottom: 4px solid #3793ca;
	}

	.zac_menu > li:first-child {
		border-top: 4px solid #3793ca;
	}

	li.cbp-tm-show > a,
	.no-touch .zac_menu > li > a:hover,
	.no-touch .zac_menu > li > a:active {
		color: #fff;
		background: #02639d;
	}

	.zac_submenu {
		position: relative;
		display: none;
		width: 100%;
	}

	.zac_submenu > li {
		padding: 0;
	}

	.zac_submenu > li > a {
		padding: 0.6em 2.3em 0.6em 0.6em;
		border: none;
		border-bottom: 2px solid #6fbbe9;
	}

	.zac_submenu:after {
		display: none;
	}

	.zac_menu .cbp-tm-show .zac_submenu {
		display: block;
		width: 100%;
		left: 0;
		margin: 0;
		padding: 0;
		bottom: auto;
		top: auto;
	}
	
}

А тут JavaScript код

;( function( window ) {
	
	'use strict';

	var document = window.document,
		docElem = document.documentElement;

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

	function getViewportH() {
		var client = docElem['clientHeight'],
			inner = window['innerHeight'];
		if( client < inner )
			return inner;
		else
			return client;
	}

	function getOffset( el ) {
		return el.getBoundingClientRect();
	}

	function isMouseLeaveOrEnter(e, handler) { 
		if (e.type != 'mouseout' && e.type != 'mouseover') return false; 
		var reltg = e.relatedTarget ? e.relatedTarget : 
		e.type == 'mouseout' ? e.toElement : e.fromElement; 
		while (reltg && reltg != handler) reltg = reltg.parentNode; 
		return (reltg != handler); 
	}

	function cbpTooltipMenu( el, options ) {	
		this.el = el;
		this.options = extend( this.defaults, options );
		this._init();
	}

	cbpTooltipMenu.prototype = {
		defaults : {
			// добавим задержку времени (небольшую)
			delayMenu : 100
		},
		_init : function() {
			this.touch = Modernizr.touch;
			this.menuItems = document.querySelectorAll( '#' + this.el.id + ' > li' );
			this._initEvents();
		},
		_initEvents : function() {
			
			var self = this;

			Array.prototype.slice.call( this.menuItems ).forEach( function( el, i ) {
				var trigger = el.querySelector( 'a' );
				if( self.touch ) {
					trigger.addEventListener( 'click', function( ev ) { self._handleClick( this, ev ); } );
				}
				else {
					trigger.addEventListener( 'click', function( ev ) {
						if( this.parentNode.querySelector( 'ul.zac_submenu' ) ) {
							ev.preventDefault();
						}
					} );
					el.addEventListener( 'mouseover', function(ev) { if( isMouseLeaveOrEnter( ev, this ) ) self._openMenu( this ); } );
					el.addEventListener( 'mouseout', function(ev) { if( isMouseLeaveOrEnter( ev, this ) ) self._closeMenu( this ); } );
				}
			} );

		},
		_openMenu : function( el ) {

			var self = this;
			clearTimeout( this.omtimeout );
			this.omtimeout = setTimeout( function() {
				var submenu = el.querySelector( 'ul.zac_submenu' );

				if( submenu ) {
					el.className = 'cbp-tm-show';
					if( self._positionMenu( el ) === 'top' ) {
						el.className += ' cbp-tm-show-above';
					}
					else {
						el.className += ' cbp-tm-show-below';
					}
				}
			}, this.touch ? 0 : this.options.delayMenu );

		},
		_closeMenu : function( el ) {
			
			clearTimeout( this.omtimeout );

			var submenu = el.querySelector( 'ul.zac_submenu' );

			if( submenu ) {
				el.className = el.className.replace(new RegExp("(^|\\s+)" + "cbp-tm-show" + "(\\s+|$)"), ' ');
				el.className = el.className.replace(new RegExp("(^|\\s+)" + "cbp-tm-show-below" + "(\\s+|$)"), ' ');
				el.className = el.className.replace(new RegExp("(^|\\s+)" + "cbp-tm-show-above" + "(\\s+|$)"), ' ');
			}

		},
		_handleClick : function( el, ev ) {
			var item = el.parentNode,
				items = Array.prototype.slice.call( this.menuItems ),
				submenu = item.querySelector( 'ul.zac_submenu' )

			// для начала закроем меню, если оно было открыто...
			if( this.current &&  items.indexOf( item ) !== this.current ) {
				this._closeMenu( this.el.children[ this.current ] );
				this.el.children[ this.current ].querySelector( 'ul.zac_submenu' ).setAttribute( 'data-open', 'false' );
			}

			if( submenu ) {
				ev.preventDefault();

				var isOpen = submenu.getAttribute( 'data-open' );

				if( isOpen === 'true' ) {
					this._closeMenu( item );
					submenu.setAttribute( 'data-open', 'false' );
				}
				else {
					this._openMenu( item );
					this.current = items.indexOf( item );
					submenu.setAttribute( 'data-open', 'true' );
				}
			}

		},
		_positionMenu : function( el ) {
			var vH = getViewportH(),
				ot = getOffset(el),
				spaceUp = ot.top ,
				spaceDown = vH - spaceUp - el.offsetHeight;
			
			return ( spaceDown <= spaceUp ? 'top' : 'bottom' );
		}
	}

	// добавляем в глобальное пространство имён
	window.cbpTooltipMenu = cbpTooltipMenu;

} )( window );

Вот такой небольшой получился код. Можете скачать исходники или просто скопировать со страницы — все работает!

На этом буду с вами прощаться. Не на долго! Заходите чаще, подписывайтесь на обновления блога, а если что-то не понятно — задавайте вопрос в комментариях.

С вами был — ваш Юрич!


Обо мне
Юрич:
Занимаюсь созданием сайтов на WordPress более 6 лет. Ранее работал в нескольких веб-студиях и решил делиться своим опытом на данном сайте. Пишите комментарии, буду рад общению.
One Comment к статье "Выпадающее меню с подсказками"
  1. Владимир Здор: 20.10.2016 в 18:45

    Как я уже упоминал, Хром поражает меня минимализмом настроек. Все они сосредоточены на одной странице с тремя вкладками, доступ к которой можно получить из выпадающего меню кнопки с тремя черточками, выбрав пункт Настройки . Что примечательно, все основные фишки этого обозревателя будут уже включенными.

Заказать сайт