Сегодня мы рассмотрим как реализовать на сайте такое же меню как в Google Nexus 7. Оно будет эффектно выскальзывать, раскрывая при этом подпункты. При проведении курсором мыши над иконкой меню мы покажем иконки боковой колонки, кликнув на которые мы полностью раскроем боковое меню.
Для создания данного меню будут использованы неупорядоченные списки ul, а также CSS переходы. Для чтобы меню правильно смотрелось на маленьких экранах зададим специальные свойства @media.
HTML код
Меню будет состоять из двух частей: первая это верхняя часть, которая в шапке, и вторая — боковое меню. Мы добавим класс gn-menu-main
к первому из них и обернем второе тегом nav. Первый элемент меню будет содержать ссылку-якорь и тег nav:
<ul id="gn-menu" class="gn-menu-main">
<li class="gn-trigger">
<a class="gn-icon gn-icon-menu"><span>Меню</span></a>
<nav class="gn-menu-wrapper">
...
</nav>
</li>
<li><a href="http://site.ru">Мегасайт</a></li>
<li> ... </li>
...
</ul>
Внутри тега nav мы добавим еще один упаковщик, который поможет нам скрыть для браузеров под Windows полосу прокрутки. Основа этого подменю – неупорядоченный список с классом gn-menu
. Он будет состоять из элементов списка, причем у нескольких из них будют свои списки. Первым элементом станет тег input для строки поиска:
<nav class="gn-menu-wrapper">
<div class="gn-scroller">
<ul class="gn-menu">
<li class="gn-search-item">
<input placeholder="Поиск..." type="search" class="gn-search">
<a class="gn-icon gn-icon-search"><span>Поиск</span></a>
</li>
<li>
<a class="gn-icon gn-icon-download">Загрузки</a>
<ul class="gn-submenu">
<li><a class="gn-icon gn-icon-illustrator">Векторная графика</a></li>
<li><a class="gn-icon gn-icon-photoshop">Photoshop файлы</a></li>
</ul>
</li>
<li><a class="gn-icon gn-icon-cog">Настройки</a></li>
<li><a class="gn-icon gn-icon-help">Помощь</a></li>
<li>
<a class="gn-icon gn-icon-archive">Архивы</a>
<ul class="gn-submenu">
<li><a class="gn-icon gn-icon-article">Статьи</a></li>
<li><a class="gn-icon gn-icon-pictures">Изображения</a></li>
<li><a class="gn-icon gn-icon-videos">Видео</a></li>
</ul>
</li>
</ul>
</div>
</nav>
CSS код
Начнем с изменения алгоритма расчета ширины и высоты элемента с помощью CSS-свойства box-sizing:
*,
*:after,
*::before {
box-sizing: border-box;
}
Для начала зададим свойства всем спискам, в том числе и вложенным:
.gn-menu-main,
.gn-menu-main ul {
margin: 0;
padding: 0;
background: white;
color: #5f6f81;
list-style: none;
text-transform: none;
font-weight: 300;
font-family: 'Lato', Arial, sans-serif;
line-height: 60px;
}
Теперь определим свойства только для главного списка. Он будет фиксирован по верху страницы и высотой в 60 пикселей:
.gn-menu-main {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
font-size: 13px;
}
Зададим стили для всех ссылок в наших меню и подменю:
.gn-menu-main a {
display: block;
height: 100%;
color: #5f6f81;
text-decoration: none;
cursor: pointer;
}
При наведении ссылки и поля ввода изменяют свой цвет:
.no-touch .gn-menu-main a:hover,
.no-touch .gn-menu li.gn-search-item:hover,
.no-touch .gn-menu li.gn-search-item:hover a {
background: #5f6f81;
color: white;
}
Дочерним элементам пункта зададим обтекание слева и однопикскльную рамку справа:
.gn-menu-main > li {
display: block;
float: left;
height: 100%;
border-right: 1px solid #c6d0da;
text-align: center;
}
Первым элементом меню будет переключатель для показа/скрытия меню. Так как мы скрываем текст и применяем к иконке меню псевдоэлемент, то установим user-select: none
, а ширину равной высоте пунктов меню.
.gn-menu-main li.gn-trigger {
position: relative;
width: 60px;
user-select: none;
}
Последний элемент списка будет иметь обтекание справа:
.gn-menu-main > li:last-child {
float: right;
border-right: none;
border-left: 1px solid #c6d0da;
}
Перейдем к обертке бокового меню-колонки. Вы спросите: зачем нам все эти обертки? Чтож, если вы не против видимой полосы прокрутки, то можно избавиться от них и просто установить у меню overflow-y: scroll
. Мы же установим для основной обертки overflow: hidden
с определенной шириной (изначально достаточной для того, чтобы было видно полосу с иконками). Затем назначим обертке чуть большую ширину и высоту в 100%. В результате наша полоса прокрутки будет скрыта, меню увеличится до нужной высоты и будет способно прокручиваться.
Сначала нам нужно скрыть меню, поэтому назначим ему отрицательное левое значение для его ширины.
.gn-menu-wrapper {
position: fixed;
top: 60px;
bottom: 0;
left: 0;
overflow: hidden;
width: 60px;
border-top: 1px solid #c6d0da;
background: white;
transform: translateX(-60px);
transition: transform 0.3s, width 0.3s;
}
.gn-scroller {
position: absolute;
overflow-y: scroll;
width: 370px;
height: 100%;
}
.gn-menu {
border-bottom: 1px solid #c6d0da;
text-align: left;
font-size: 18px;
}
Для разделения пунктов меню мы добавим тень блока. Это позволит нам избежать двойных линий при скрывании элементов подменю:
.gn-menu li:not(:first-child),
.gn-menu li li {
box-shadow: inset 0 1px #c6d0da
}
Добавим переход к пунктам списка подменю и сделаем их начальную высоту равной нулю:
.gn-submenu li {
overflow: hidden;
height: 0;
transition: height 0.3s;
}
Цвет цвет ссылок у элементов подменю будет чуть светлее, чем у элементов родительского меню:
.gn-submenu li a {
color: #c1c9d1;
}
Запишем теперь стили для элемента «Поиск». Нам нужно чтобы он выглядел также, как настранице Google Nexus, поэтому сделаем у него прозрачный фон и тексты-подсказки такие же как обычные элементы меню:
input.gn-search {
position: relative;
z-index: 10;
padding-left: 60px;
outline: none;
border: none;
background: transparent;
color: #5f6f81;
font-weight: 300;
font-family: Arial, sans-serif;
cursor: pointer;
}
.gn-search::-webkit-input-placeholder {
color: #5f6f81;
}
.gn-search:-moz-placeholder {
color: #5f6f81;
}
.gn-search::-moz-placeholder {
color: #5f6f81;
}
.gn-search:-ms-input-placeholder {
color: #5f6f81;
}
Для атрибута placeholder нам нужно также задать стили. Дело в том, что большинстве браузеров текст-подсказка будет скрываться при щелчке по элементу input. У браузера Chrome такое поведение отсутствует, поэтому мы пойдем на хитрость и установим прозрачным цвет текста-подсказки когда фокус находится в поле input:
.gn-search:focus::-webkit-input-placeholder,
.no-touch .gn-menu li.gn-search-item:hover .gn-search:focus::-webkit-input-placeholder {
color: transparent;
}
input.gn-search:focus {
cursor: text;
}
При проведении мышью мы меняем цвет текста input на белый, точно так же, как для остальных ссылок-якорей (это тот текст, который вводит пользователь):
.no-touch .gn-menu li.gn-search-item:hover input.gn-search {
color: white;
}
То же самое сделаем с текстом-подсказкой:
.no-touch .gn-menu li.gn-search-item:hover .gn-search::-webkit-input-placeholder {
color: white;
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search:-moz-placeholder {
color: white;
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search::-moz-placeholder {
color: white;
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search:-ms-input-placeholder {
color: white;
}
Ссылка-якорь иконки поиска будет особенной, потому что у нее не будет рядом видимого текста. Весь элемент списка – это ловкий трюк. Видите ли, установив якорь иконки на position: absolute
, мы позволим своему поисковому input начаться прямо с самого левого края элемента списка. Но помните, у нашего input большой левый отступ, поэтому текст будет начинаться только после иконки поиска. При щелчке по иконке поиска на самом деле мы щелкаем на input, вводя его в фокус.
.gn-menu-main a.gn-icon-search {
position: absolute;
top: 0;
left: 0;
height: 60px;
}
Теперь давайте назначим стили псевдоэлементу иконок::before. Мы установим их на display: inline-block
и назначим ширину 60 px. Нам придется сбросить все стили шрифтов, так как сейчас мы воспользуемся своим шрифтом-иконкой, включенным в начало CSS:
.gn-icon::before {
display: inline-block;
width: 60px;
text-align: center;
text-transform: none;
font-weight: normal;
font-style: normal;
font-variant: normal;
font-family: 'ecoicons';
line-height: 1;
speak: none;
-webkit-font-smoothing: antialiased;
}
Давайте назначим всем иконкам содержимое:
.gn-icon-help::before {
content: "\e000";
}
.gn-icon-cog::before {
content: "\e006";
}
...
.gn-icon-pictures::before {
content: "\e008";
}
.gn-icon-videos::before {
content: "\e009";
}
Нам нужно, чтобы текст ссылки-якоря был виден рядом с иконкой, но иногда требуется показывать только иконку. Но нам нужна не только пустая ссылка-якорь, сам текст тоже должен быть в HTML. Поэтому мы обернем эти отдельные случаи тегом span, который просто скроем, установив ширину и высоту на 0, а overflow – на hidden. Почему просто не воспользоваться display: none
? Скрыв подобный контент, мы сделаем его недоступным для экранных дикторов:
.gn-iconspan {
width: 0;
height: 0;
display: block;
overflow: hidden;
}
И не забудем о маленькой иконке в основном меню. Итак, мы не станем здесь применять иконку из шрифта, хотя могли бы, конечно. Вместо этого мы создадим ее с помощью тени блока и применим контрастные цвета (фоновый и синий) для создания трех линий. Также, если хотите, можно применить здесь градиент.
.gn-icon-menu::before {
margin-left: -15px;
vertical-align: -2px;
width: 30px;
height: 3px;
background: #5f6f81;
box-shadow: 0 3px white,
0 -6px #5f6f81,
0 -9px white,
0 -12px #5f6f81;
content: '';
}
При наведении курсором мыши мы меняем цвета тени блока на обратные:
.no-touch .gn-icon-menu:hover::before,
.no-touch .gn-icon-menu.gn-selected:hover::before {
background: white;
box-shadow: 0 3px #5f6f81, 0 -6px white, 0 -9px #5f6f81, 0 -12px white;
}
А при выборе (когда боковое меню открыто) сделаем его еще более синим:
.gn-icon-menu.gn-selected::before {
background: #5993cd;
box-shadow: 0 3px white,
0 -6px #5993cd,
0 -9px white,
0 -12px #5993cd;
}
Последнее, что нужно сделать – это определить два класса для открытия меню и показа только иконок и всего меню в целом. При проведении мышью над иконкой меню мы покажем только иконки. Назовем его gn-open-part
. Другой класс, gn-open-all
, будет применяться при щелчке на иконку главного меню, или же при наведении курсора мыши на видимую часть иконки (самим боковым меню). В обоих классах требуется обнулить translate:
.gn-menu-wrapper.gn-open-all,
.gn-menu-wrapper.gn-open-part {
transform: translateX(0px);
}
Чтобы открыть меню полностью нужно установить правильную ширину:
.gn-menu-wrapper.gn-open-all {
width: 340px;
}
При открытии всего меню полностью элементы подменю также будут увеличиваться:
.gn-menu-wrapper.gn-open-all .gn-submenu li {
height: 60px;
}
И незабудьте про медиазапрос, который заставит наше меню использовать всю ширину экрана:
@media screen and (max-width: 422px) {
.gn-menu-wrapper.gn-open-all {
transform: translateX(0px);
width: 100%;
}
.gn-menu-wrapper.gn-open-all .gn-scroller {
width: 130%;
}
}
Также мы приспособим ширину обертки, скрывающей прокрутку так, чтобы она стала больше 100%. Это, возможно, не очень важно, так как по большей части в устройствах такого размера мы не видим полосу прокрутки. Итак, со стилизацией тегов покончено и теперь применим немного JavaScript для открытия и закрытия меню (применяя соответствующие классы).
Javascript код
Итак, давайте создадим маленький скрипт, который позаботится о функциональности меню. Нам нужно чтобы при проведении мышью над иконкой меню, первая часть его выскальзывала так, чтобы были видны иконки. Если провести мышью над областью боковой колонки или щелкнуть на иконку главного меню, то выскользнет остаток меню. Еще один щелчок по иконке меню или любой другой области – и меню уберется обратно. Так что давайте посмотрим, как все это можно выполнить.
Начнем с кэширования некоторых элементов и инициализации нескольких переменных. Функция bodyClickFn
определяет, что случится, когда меню открыто, а мы щелкаем по документу где-то в другом месте. Также нужно позаботиться о событиях касания.
_init : function() {
this.trigger = this.el.querySelector('a.gn-icon-menu');
this.menu = this.el.querySelector('nav.gn-menu-wrapper');
this.isMenuOpen = false;
this.eventtype = mobilecheck() ? 'touchstart' : 'click';
this._initEvents();
var self = this;
this.bodyClickFn = function() {
self._closeMenu();
this.removeEventListener(self.eventtype, self.bodyClickFn);
}
Давайте рассмотрим события, требующие инициализации. Нам нужно открыть первую часть меню (давайте назовем ее иконным меню), когда над иконкой главного меню (триггером) проводят мышью. При уходе мыши это самое меню должно убираться обратно.
this.trigger.addEventListener(
'mouseover',
function(ev) {self._openIconMenu();}
);
this.trigger.addEventListener(
'mouseout',
function(ev) {self._closeIconMenu();}
);
Как только меню-иконка окажется в окне просмотра, проведение над ним мышью заставит выскользнуть остаток меню. Если после его появления щелкнуть где-нибудь по странице, меню снова должно убраться из виду. Нам нужно привязать к документу соответствующее событие (щелчок или касание).
this.menu.addEventListener(
'mouseover',
function(ev) {
self._openMenu();
document.addEventListener(self.eventtype,self.bodyClickFn);
}
);
Наконец, нужно, чтобы при щелчке по иконке меню оно полностью выдвигалось или убиралось, если уже находится в окне просмотра. Кроме того, мы привяжем к документу (или отвяжем) соответствующее событие (щелчок или касание).
this.trigger.addEventListener(this.eventtype, function(ev) {
ev.stopPropagation();
ev.preventDefault();
if(self.isMenuOpen) {
self._closeMenu();
document.removeEventListener(self.eventtype, self.bodyClickFn);
}
else{
self._openMenu();
document.addEventListener(self.eventtype, self.bodyClickFn);
}
});
И последнее: нам не требуется, чтобы меню убиралось при щелчке где-нибудь внутри области меню. При привязке к документу события щелчка/касания (для того, чтобы закрыть меню) нужно сделать следующее:
this.menu.addEventListener(
this.eventtype,
function(ev) {
ev.stopPropagation();
}
);
Окончательный код функции _initEvents
мы приводить не будем (ибо он слишком большой). Если вам интересно, вы можете увидеть его в примере. Запуск скрипта меню довольно прост:
<script>
new gnMenu(document.getElementById('gn-menu'));
</script>
Вот и все на сегодня. До следующих уроков!