От автора: Учебник о том, как воссоздать выскальзывающее боковое меню-колонку, которое можно увидеть на вебсайте GoogleNexus 7.
Сегодня я хочу показать вам, как реконструировать боковое меню-колонку страницы Google Nexus 7. Оно выскальзывает с красивым эффектом, и еще при этом раскрываются подпункты. При проведении мышью над специальной иконкой меню показываются иконки боковой колонки. При щелчке по иконке боковое меню будет показываться полностью. Первый элемент бокового меню-колонки – это поле ввода для поиска, стили которого такие же, как у остальных пунктов.
Реконструкция этого меню будет проводиться с помощью неупорядоченных вложенных списков и нескольких переходов CSS. Мы воспользуемся JavaScript’ом для применения классов к эффектам открытия и обработки событий проведения мышью и щелчка. С помощью медиазапроса мы «подгоним» размер, чтобы подходило для маленьких устройств. Итак, давайте начнем!
Разметка
Наше меню будет состоять из двух основных частей: главного, того, которое будет находиться вверху, как верхний колонтитул, и бокового. Мы добавим класс “gn-menu-main” к первому из них и обернем второе в элемент nav. Вы, конечно, можете использовать любую предпочитаемую вами структуру. Первый элемент меню будет содержать ссылку-якорь и элемент nav:
1 2 3 4 5 6 7 8 9 10 11 |
<ul id="gn-menu" class="gn-menu-main"> <li class="gn-trigger"> <a class="gn-icon gn-icon-menu"><span>Menu</span></a> <nav class="gn-menu-wrapper"> <!-- ... --> </nav> </li> <li><ahref="//tympanus.net/codrops">Codrops</a></li> <li><!-- ... --></li> <!-- ... --> </ul> |
Внутри элемента nav мы добавим еще один упаковщик, который поможет нам скрыть для браузеров Windows некрасивую полосу прокрутки. Основа этого подменю – неупорядоченный список с классом “gn-menu”. Он будет состоять из элементов списка, у нескольких из них имеется субсписок. Первым элементом станет специальный input поиска:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<div class="gn-scroller"> <ul class="gn-menu"> <li class="gn-search-item"> <input placeholder="Search" type="search" class="gn-search"> <a class="gn-icon gn-icon-search"><span>Search</span></a> </li> <li> <a class="gn-icon gn-icon-download">Downloads</a> <ul class="gn-submenu"> <li><a class="gn-icon gn-icon-illustrator">Vector Illustrations</a></li> <li><a class="gn-icon gn-icon-photoshop">Photoshop files</a></li> </ul> </li> <li><a class="gn-icon gn-icon-cog">Settings</a></li> <li><!-- ... --></li> <!-- ... --> </ul> </div><!-- /gn-scroller --> |
Теперь давайте назначим стили.
CSS
Обратите внимание, чnо в CSS не будет вендорных префиксов, но вы найдете их в файлах. Начнем с установки border-box для всех box-sizing:
1 2 3 4 5 |
*, *:after, *::before { box-sizing: border-box; } |
Так как мы применим шрифты-иконки, то перейдем к IcoMoon и выберем красивые иконки из набора EcoIco Мэтью Скайлза (MatthewSkiles).
1 2 3 4 5 6 7 |
@font-face { font-weight: normal; font-style: normal; font-family: 'ecoicons'; src: url("../fonts/ecoicons/ecoicons.eot"); src: url("../fonts/ecoicons/ecoicons.eot?#iefix") format("embedded-opentype"), url("../fonts/ecoicons/ecoicons.woff") format("woff"), url("../fonts/ecoicons/ecoicons.ttf") format("truetype"), url("../fonts/ecoicons/ecoicons.svg#ecoicons") format("svg"); } |
Позже для добавления иконок к якорям мы применим псевдоэлемент. Но до того давайте назначим стили всем спискам:
1 2 3 4 5 6 7 8 9 10 11 12 |
.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 пикселей:
1 2 3 4 5 6 7 8 |
.gn-menu-main { position: fixed; top: 0; left: 0; width: 100%; height: 60px; font-size: 13px; } |
Общий стиль всех ссылок в наших меню и подменю будет следующим:
1 2 3 4 5 6 7 |
.gn-menu-main a { display: block; height: 100%; color: #5f6f81; text-decoration: none; cursor: pointer; } |
Так же определим несколько стилей состояния проведения мышью, где мы меняем цвета. Первому пункту бокового меню-колонки, где у нас будет поле поиска, понадобится специальный стиль при проведении мышью. Здесь у нас не будет ссылки, заполняющей весь пункт, поэтому давайте определим состояние проведения мышью к li и проконтролируем, что случится с иконкой (якорем) и самим li:
1 2 3 4 5 6 |
.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; } |
Дочерние элементы пункта станут float: left, и у них будет border-right:
1 2 3 4 5 6 7 |
.gn-menu-main > li { display: block; float: left; height: 100%; border-right: 1px solid #c6d0da; text-align: center; } |
Первый пункт списка будет специальным пусковым элементом и, так как мы скроем текст и применим к иконке меню псевдоэлемент, то установим user-select на none, а ширину такую же, как высота пунктов.
1 2 3 4 5 |
.gn-menu-main li.gn-trigger { position: relative; width: 60px; user-select: none; } |
Последний пункт нашего основного списка будет float: right и мы поменяем border-right:
1 2 3 4 5 |
.gn-menu-main >li:last-child { float: right; border-right: none; border-left: 1px solid #c6d0da; } |
У ссылок главного меню будет отступ, а тексту мы назначим немного другие стили:
1 2 3 4 5 6 |
.gn-menu-main > li > a { padding: 030px; text-transform: uppercase; letter-spacing: 1px; font-weight: bold; } |
Очистим плавающие элементы с помощью следующего микрохака clearfix Николаса Галлахера (NicolasGallagher):
1 2 3 4 5 |
.gn-menu-main:after { display: table; clear: both; content: ''; } |
Отлично, теперь не хватает только стиля иконки меню, но давайте отложим его на потом, когда определим другие псевдоклассы иконки.
Перейдем к упаковщику бокового меню-колонки. Зачем нам эти дополнительные упаковщики? Ну, если вы не против видимой полосы прокрутки, то можно отделаться от них и просто установить меню на overflow-y: scroll. Но так как полоса прокрутки действительно сильно портит дизайн в браузерах на Windows, то для ее скрытия мы проделаем маленький фокус. Установим основной упаковщик на overflow hidden с определенной шириной (изначально достаточной для того, чтобы было видно полосу с иконками). Затем назначим упаковщику прокрутки чуть большую ширину и высоту в 100%. Полоса прокрутки будет скрыта. Наше меню затем увеличится до нужной высоты и будет способно прокручиваться.
Сначала нам нужно скрыть меню, поэтому назначим ему отрицательное левое значение (его ширины).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
.gn-menu-wrapper { position: fixed; top: 60px; bottom: 0; left: 0; overflow: hidden; width: 60px; /* будет сделан переход до 340px */ border-top: 1px solid #c6d0da; background: white; transform: translateX(-60px); /* будет сделан переход до 0px */ transition: transform 0.3s, width 0.3s; } .gn-scroller { position: absolute; overflow-y: scroll; width: 370px; height: 100%; } .gn-menu { border-bottom: 1pxsolid#c6d0da; text-align: left; font-size: 18px; } |
Для разделения пунктов списка давайте добавим тень блока. Так нам удастся избежать двойных линий при скрывании элементов подменю:
1 2 3 4 |
.gn-menu li:not(:first-child), .gn-menu li li { box-shadow: inset 01px #c6d0da } |
Добавим переход к пунктам списка подменю и установим их начальную высоту на 0:
1 2 3 4 5 |
.gn-submenu li { overflow: hidden; height: 0; transition: height 0.3s; } |
Цвет будет немного светлее, чем у элементов родительского меню:
1 2 3 |
.gn-submenu li a { color: #c1c9d1 } |
Теперь назначим стили специальному элементу поиска и особенно его input. Нам нужно, чтобы они были действительно тонкими, как настранице Google Nexus, поэтому назначим им прозрачные фоновые цвета и сделаем так, чтобы тексты-подсказки выглядели, как обычные элементы меню:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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; } /* заполнитель */ .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 } |
В большинстве браузеров текст-подсказка будет скрываться при щелчке по input, отчего пользователям будет гораздо легче понять, что это собственно input. У Chrome такое поведение отсутствует, так что мы воспользуемся небольшим приемом для симуляции того же самого, установив цвет текста-подсказки на прозрачный при щелчке пользователя по input и фокусированию на нем:
1 2 3 4 5 6 7 8 |
.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 на белый, точно так же, как для остальных ссылок-якорей (это тот текст, который вводит пользователь):
1 2 3 |
.no-touch .gn-menu li.gn-search-item:hover input.gn-search { color: white } |
То же самое сделаем с текстом-подсказкой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* текст-подсказка */ .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, вводя его в фокус.
1 2 3 4 5 6 |
.gn-menu-main a.gn-icon-search { position: absolute; top: 0; left: 0; height: 60px; } |
Теперь давайте назначим стили псевдоэлементу иконок::before. Мы установим их на inline-block и назначим ширину 60 px. Нам придется сбросить все стили шрифтов, так как сейчас мы воспользуемся своим шрифтом-иконкой, включенным в начало CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.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; } |
Давайте назначим всем иконкам содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
.gn-icon-help::before { content: "\e000" } .gn-icon-cog::before { content: "\e006" } .gn-icon-search::before { content: "\e005" } .gn-icon-download::before { content: "\e007" } .gn-icon-photoshop::before { content: "\e001" } .gn-icon-illustrator::before { content: "\e002" } .gn-icon-archive::before { content: "\e00d" } .gn-icon-article::before { content: "\e003" } .gn-icon-pictures::before { content: "\e008" } .gn-icon-videos::before { content: "\e009" } |
В норме нам нужно, чтобы текст ссылки-якоря виден рядом с иконкой, но иногда требуется показывать только иконку. Но нам нужна не только пустая ссылка- якорь, но и текст должен быть в HTML. Поэтому мы обернем эти отдельные случаи в диапазон, который просто скроем, установив ширину и высоту на 0, а overflow – на hidden. Почему просто не воспользоваться display: none? Скрыв подобный контент, мы сделаем его недоступным для экранных дикторов, поэтому давайте убедимся, что не «стерли» ничего для них важного:
1 2 3 4 5 6 |
.gn-iconspan { width: 0; height: 0; display: block; overflow: hidden; } |
И не забудем о маленькой иконке в основном меню. Итак, мы не станем здесь применять иконку из шрифта, хотя могли бы, конечно. Вместо этого мы создадим ее с помощью тени блока и применим контрастные цвета (фоновый и синий) для создания трех линий. Также, если хотите, можно применить здесь градиент.
1 2 3 4 5 6 7 8 9 |
.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: ''; } |
При проведении мышью мы меняем цвета тени блока на обратные:
1 2 3 4 5 |
.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; } |
А при выборе (когда боковое меню открыто) сделаем его еще более синим:
1 2 3 4 |
.gn-icon-menu.gn-selected::before { background: #5993cd; box-shadow: 03pxwhite, 0-6px#5993cd, 0-9pxwhite, 0-12px#5993cd; } |
Последнее, что нужно сделать – это определить два класса для открытия меню и показа только иконок и всего меню в целом. При проведении мышью над иконкой меню мы покажем только иконки. Давайте назовем этот класс gn-open-part. Другой класс, gn-open-all, будет применяться при щелчке на иконку главного меню, или же проведении мышью над видимой частью иконки (самим боковым меню). В обоих классах требуется сбросить translate до 0:
1 2 3 4 |
.gn-menu-wrapper.gn-open-all, .gn-menu-wrapper.gn-open-part { transform: translateX(0px); } |
Если требуется открыть меню полностью, нам придется установить правильную ширину:
1 2 3 |
.gn-menu-wrapper.gn-open-all{ width: 340px; } |
При открытии всего меню полностью элементы подменю также будут увеличиваться:
1 2 3 |
.gn-menu-wrapper.gn-open-all.gn-submenu li { height: 60px; } |
И последнее, но от этого не менее важное – наш драгоценный медиазапрос, который заставит меню использовать всю ширину экрана:
1 2 3 4 5 6 7 8 9 10 |
@media screenand (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 определяет, что случится, когда меню открыто, а мы щелкаем по документу где-то в другом месте. Также нужно позаботиться о событиях касания.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
_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(); varself = this; this.bodyClickFn = function() { self._closeMenu(); this.removeEventListener( self.eventtype, self.bodyClickFn ); }; } |
Давайте рассмотрим события, требующие инициализации. Нам нужно открыть первую часть меню (давайте назовем ее иконным меню), когда над иконкой главного меню (триггером) проводят мышью. При уходе мыши это самое меню должно убираться обратно.
1 2 |
this.trigger.addEventListener( 'mouseover', function(ev) { self._openIconMenu(); } ); this.trigger.addEventListener( 'mouseout', function(ev) { self._closeIconMenu(); } ); |
Как только меню-иконка окажется в окне просмотра, проведение над ним мышью заставит выскользнуть остаток меню. Если после его появления щелкнуть где-нибудь по странице, меню снова должно убраться из виду. Нам нужно привязать к документу соответствующее событие (щелчок или касание).
1 2 3 4 |
this.menu.addEventListener( 'mouseover', function(ev) { self._openMenu(); document.addEventListener( self.eventtype, self.bodyClickFn ); } ); |
Наконец, нужно, чтобы при щелчке по иконке меню оно полностью выскальзывало или убиралось, если уже находится в окне просмотра. Кроме того, мы привяжем к документу(или отвяжем) соответствующее событие (щелчок или касание).
1 2 3 4 5 6 7 8 9 10 11 12 |
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 ); } } ); |
И последнее: нам не требуется, чтобы меню убиралось при щелчке где-нибудь внутри области меню. При привязке к документу события щелчка/касания (для того, чтобы закрыть меню) нужно сделать следующее:
1 |
this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } ); |
А вот окончательная функция_initEvents и способы открытия и закрытия меню.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
_initEvents : function() { varself = 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(); } |
Вот и все! Благодарю вас за прочтение статьи и надеюсь, что вам понравился этот учебник и оказался полезным!
Автор: Mary Lou
Источник: //tympanus.net/
Редакция: Команда webformyself.