Создаем меню в стиле сайта Google Nexus 7

Мы воспользуемся javascript, чтобы применить классы для эффектов открывания и управления событиями при наведении и нажатии. И так, давайте приступим.

Шаг 1. HTML
Наше меню будет состоять их 2 основных элементов: основное меню – то, которое вы можете видеть вверху страницы в виде шапки сайта, и боковое меню. Первому меню мы зададим класс gn-menu-main, и обернем второе меню в элемент ‘nav’. Конечно же, вы можете использовать ту структуру, которую вам хочется.

Код
<ul class="gn-menu-main">
<ul 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="#">Get-element</a></li>
  <li><!-- ... --></li>
</ul>
</ul>
 
<ul class="gn-menu-main" id="gn-menu"><!-- ... --></ul>


Ядром нашего меню будет ненумерованный список с классом gn-menu. Он будет состоять из элементов списка, некоторые из которых будут иметь дополнительный список. Первый пункт меню будет представлен в виде строки поиска:

Код
<div class="gn-scroller">
<ul class="gn-menu">
<ul class="gn-menu">
  <li class="gn-search-item"><input class="gn-search" type="search" placeholder="Поиск..." />
  <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><!-- ... --></li>
</ul>
</ul>
<ul class="gn-menu"><!-- ... --></ul>
</div>
 
<!-- /gn-scroller --> 

С разметкой разобрались, давайте перейдем к следующему шагу.

Шаг 2. CSS
Давайте начнем с указания border-box для всех свойств box-sizing:

Код
*,
*:after,
*::before {
  box-sizing: border-box;
}

Так как мы будем использовать иконический шрифт для иконок, нам нужно открыть IcoMoon, и выбрать несколько красивых иконок из набора Eco Ico от Matthew Skiles.

Код
@font-face {
  font-weight: normal;
  font-style: normal;
  font-family: 'ecoicons';

Для начала давайте оформим все списки:

Код
.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;
}

Здесь у нас не будет анкора, который будет заполнять пункт списка, поэтому давайте определим hover-стиль для li и укажем, что будет происходить с иконкой (анкором) и самим элементом li:

Код
.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 &amp;gt; li {
  display: block;
  float: left;
  height: 100%;
  border-right: 1px solid #c6d0da;
  text-align: center;
}

Мы установим свойство user-select на none, а свойству ширины (width) зададим то же значение, что указано в высоте всего пункта.

Код
.gn-menu-main li.gn-trigger {
  position: relative;
  width: 60px;
  user-select: none;
}

Последний пункт нашего основного списка будет выравнен по правому краю, и нам также нужно будет слегка изменить границу:

Код
.gn-menu-main &amp;gt; li &amp;gt; a {
  padding: 0 30px;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: bold;
}

Анкоры основного меню будут оформлены небольшими отступами, а также мы применим немного другой стиль текста:

Код
.gn-menu-main &amp;gt; li &amp;gt; a {
  padding: 0 30px;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: bold;
}

Давайте удалим выравнивания при помощи следующего миниатюрного хака clearfix от Nicolas Gallagher:

Код
.gn-menu-main:after {
  display: table;
  clear: both;
  content: '';
}

Изначально нам нужно скрыть меню, поэтому мы зададим ему значение negative left (равное его ширине):

Код
.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
}

Давайте добавим переход к пунктам списка дополнительного меню, и выставим их изначальную высоту на 0:

Код
.gn-submenu li {
  overflow: hidden;
  height: 0;
  transition: height 0.3s;
}

Цвет здесь будет немного светлее родительских пунктов меню:

Код
.gn-submenu li a {
  color: #c1c9d1
}

Теперь давайте стилизуем отдельный пункт с поиском и поле ввода запроса. Нам нужно, чтобы оно было едва заметным, как это реализовано на странице Google Nexus, поэтому мы зададим ему прозрачный цвет фона и сделаем так, чтобы placeholder-элементы выглядели как обычные пункты меню:

Код
input.gn-search {
  position: relative;
  z-index: 10;
  padding-left: 60px;
  outline: none;
  border: none;
  background: transparent;
  color: #5f6f81;
  font-weight: 300;
  font-family: 'Lato', Arial, sans-serif;
  cursor: pointer;
}
/* placeholder */
.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 при клике по полю ввода, что гораздо лучше дает пользователям понять, что это поле ввода. В Chrome такого не наблюдается, поэтому мы воспользуемся этим небольшим трюком для симуляции подобного эффекта, установив цвет placeholder на прозрачный после того, как пользователь кликает по полю ввода:

Код
.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
}

При наведении мы изменяем цвет текста ввода на белый, - то же самое, что мы делали на других анкорах (это текст, который вводит пользователь):

Код
.no-touch .gn-menu li.gn-search-item:hover input.gn-search {
  color: white
}

То же самое мы делаем и для текста placeholder:

Код
/* placeholder */
.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
}

Когда мы кликаем по иконке поиска, мы на самом деле кликаем по полю ввода, переводя на него фокусировку браузера.

Код
.gn-menu-main a.gn-icon-search {
  position: absolute;
  top: 0;
  left: 0;
  height: 60px;
}

Теперь давайте оформим псевдо элемент ::before для иконок. Мы зададим им параметр inline-block и ширину в 60 пикселей. Нам нужно сбросить все стили шрифта, так как сейчас мы будем использовать наш иконический шрифт, который мы указали в самом начале нашего 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: &amp;quot;e000&amp;quot;
}
.gn-icon-cog::before {
  content: &amp;quot;e006&amp;quot;
}
.gn-icon-search::before {
  content: &amp;quot;e005&amp;quot;
}
.gn-icon-download::before {
  content: &amp;quot;e007&amp;quot;
}
.gn-icon-photoshop::before {
  content: &amp;quot;e001&amp;quot;
}
.gn-icon-illustrator::before {
  content: &amp;quot;e002&amp;quot;
}
.gn-icon-archive::before {
  content: &amp;quot;e00d&amp;quot;
}
.gn-icon-article::before {
  content: &amp;quot;e003&amp;quot;
}
.gn-icon-pictures::before {
  content: &amp;quot;e008&amp;quot;
}
.gn-icon-videos::before {
  content: &amp;quot;e009&amp;quot;
}

В целом, нам нужно, чтобы текст анкора отображался рядом с иконкой, но иногда нам нужно бдует просто отобразить иконку. Но нам не нужен просто пустой анкор, в HTML-коде должен быть текст. Поэтому мы оборачиваем эти особые случаи в span-элемент, который затем скрываем, устанавливая ширину и высоту на 0, а свойство overflow на hidden. Почему бы просто не использовать display: none? Скрывание контента таким образом может привести к тому, что его невозможно будет достать через экранные читалки, поэтому давайте удостоверимся в том, что мы не «стираем» что-либо важное для них:

Код
.gn-icon span {
  width: 0;
  height: 0;
  display: block;
  overflow: hidden;
}

Давайте также не забывать о нашей небольшой иконке меню в нашем основном меню. Итак, здесь мы не будем использовать иконку из иконического шрифта, но тем не менее, вы, конечно же, можете это сделать. Вместо этого мы создадим ее при помощи box-shadow, который будет изменять цвета (фоновый цвет и синий цвет), чтобы создать иллюзию 3 линий. Здесь вы по желанию также можете использовать градиент.

Код
.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 на 0:

Код
.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 query, который позволит сделать так, что меню будет использовать всю ширину экрана:

Код
@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 для логического процесса открывания и закрывания меню (то есть, для применения классов).

Шаг 3. JS
Итак, давайте создадим небольшой скрипт, который позаботится о функционале меню. Когда мы наводим на иконку меню, нам нужно, чтобы первая часть меню выезжала, чтобы мы могли видеть иконки. Если мы наводим на область бокового меню или кликаем по иконке основного меню, тогда перед нами появляется все остальное. Повторное нажатие по иконке меню или нажатие по любой другой области на всей странице приводит к тому, что меню закрывается обратно. Давайте посмотрим, как мы можем все это реализовать.

Мы начинаем с кеширования элементов и инициализации некоторых переменных. Функция bodyClickFn определяет, что происходит, когда меню открыто и мы кликаем по какой-либо обалсти в документе. Нам также следует позаботиться о событиях touch.

Код
_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(); } );

Как только меню иконок окажется в окне просмотра, наведении по нему приведет к открытию полной версии меню. После того, как оно будет отображено, и мы кликнем где-либо в документе, меню обратно закроется. Нам нужно привязать соответствующее событие (click или touchstart) к документу.

Код
this.menu.addEventListener( 'mouseover', function(ev) {
  self._openMenu();
  document.addEventListener( self.eventtype, self.bodyClickFn );
} );

Наконец, если мы кликаем по иконке меню, нам нужно, чтобы все меню было отображено. Нам также нужно привязать соответствующее событие (click или touchstart).

Код
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 );
  }
} );

И здесь предложена последняя функция _initEvents и методы для открывания и закрывания меню.

Код
_initEvents : function() {
  var self = this;
  if( !mobilecheck() ) {
  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(); } );
},
_openIconMenu : function() {
  classie.add( this.menu, 'gn-open-part' );
},
_closeIconMenu : function() {
  classie.remove( this.menu, 'gn-open-part' );
},
_openMenu : function() {
  if( this.isMenuOpen ) return;
  classie.add( this.trigger, 'gn-selected' );
  this.isMenuOpen = true;
  classie.add( this.menu, 'gn-open-all' );
  this._closeIconMenu();
},
_closeMenu : function() {
  if( !this.isMenuOpen ) return;
  classie.remove( this.trigger, 'gn-selected' );
  this.isMenuOpen = false;
  classie.remove( this.menu, 'gn-open-all' );
  this._closeIconMenu();
}

Вот и все. Готово!

  • FalleN

  • 4005

  • 1

  • 287

Ссылки на статью:

Похожие статьи: