Меню в стиле сайта Google Nexus

demosourse

Сегодня я хочу показать вам, как можно воссоздать боковое меню со страницы Google Nexus 7. Выезжание меню сопровождается приятным эффектом, при котором раскрываются также и подпункты меню. При наведении на специализированную иконку меню отобразятся иконки сайдбара. При клике на иконку будет отображено все меню целиком. Первый пункт меню - это строка поиска, которая стилизована так же, как и все остальные элементы меню.

Мы реализуем это меню с использованием неупорядоченных вложенных списков и некоторых эффектов перехода CSS. Также мы используем JavaScript для добавления классов элементам и обработки событий наведения и клика по элементам. C помощью медиа-запросов мы будем подстраивать размеры меню для устройств с малой диагональю экрана.

Ну что ж, начнем!

Разметка

Наше меню будет состоять из двух основных частей: основное меню, которое вы можете видеть вверху наряду с заголовком, и боковое меню. Первому мы зададим класс “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>Menu</span></a>
        <nav class="gn-menu-wrapper">
            <!-- ... -->
        </nav>
    </li>
    <li><a href="http://tympanus.net/codrops">Codrops</a></li>
    <li><!-- ... --></li>
    <!-- ... -->
</ul>

Внутри элемента nav мы добавим еще один блок-обертку, который поможет спрятать надоедливую полосу прокрутки в браузерах Windows. Сердцем этого подменю является неупорядоченный список с классом “gn-menu”. Он будет состоять из элементов списка, некоторые из которых будут включать в себя другие списки. Первым элементом будет специальный элемент для поиска:

<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

Заметьте, что в этом CSS не указано никаких вендорных префиксов, но в исходниках они есть.

Начнем с выставления свойству box-sizing значения border-box.

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

В нашем примере мы будем использовать иконочный шрифт IcoMoon и некоторые иконки из набора иконок Eco Ico.

@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");
}

Позже мы используем псевдо-элемент, чтобы добавить иконки пунктам меню. Но сначала зададим стили для списков:

.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;
}

Это общие (сбрасывающие) стили для списков и вложенных списков.

Теперь давайте определим стили для верхней панели. Она будет зафиксирована вверху страницы, и иметь высоту 60px:

.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;
}

Также давайте определим стили для элементов при наведении, для них мы будем инвертировать цвета. Для первого пункта меню, который является полем ввода текста для поиска, нам нужны будут особые стили при наведении курсора на элемент. В этом случае у нас не будет опорного элемента, который занимает всю его площадь, так что привяжем наши стили к элементу <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 > 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;
}

Ссылки бокового меню будут иметь небольшие поля, а их текст мы отформатируем слегка иначе:

.gn-menu-main > li > a {
    padding: 0 30px;
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: bold;
}

Уберем обтекание элементов с помощью небольшого хака:

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

Отлично, теперь не хватает только стиля для иконки переключения меню, но сделаем это потом, когда зададим стили для других иконок.

Перейдем к обертке для бокового меню. Для чего нам нужны эти дополнительные обертки? Если вы не против полосы прокрутки - можно просто задать контейнеру свойство overflow-y: scroll. В этом случае вы сможете прокручивать меню колесом мыши, если не все пункты меню влезают в экран, но в этом случае будет видна совершенно не вписывающаяся в дизайн полоса прокрутки. Но так как полоса прокрутки ломает весь наш минималистичный дизайн, мы применим небольшой трюк для того, чтобы спрятать её. Для начала мы установим основной обертке свойство overflow: hidden, начиная с определенной ширины (изначально сделаем панель достаточной ширины, чтобы были видны иконки). Далее мы зададим прокручиваемой обертке ширину и высоту немного больше 100%. Таким образом полосы прокрутки останутся за границами видимой области, а меню может быть сколь угодно большой высоты - у нас останется возможность прокрутки в пределах нашей обертки.

Изначально нам надо спрятать меню, так что зададим ему отрицательную величину сдвига влево (равную его ширине). Для этого используем трансформацию смещения.

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

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
}

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

.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 */

.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 для наших иконок. Сделаем его строчно-блочным, с шириной в 60px. Сбросим ему все текстовые стили, так как мы будем использовать свой иконочный шрифт, который мы подключили в начале файла стилей:

.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-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. Так что обернем такой особый элемент в span, который мы просто спрячем, задав ему ширину и высоту, равную 0, и overflow : hidden. Почему не использовать просто display: none? Пряча таким образом содержимое, мы делаем его недоступным для экранных дикторов, так что давайте позаботимся о том, чтобы не удалять со страницы важную для них информацию.

.gn-icon span {
    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 - будет применен, если мы кликнем по иконке главного меню, или если наведем на панель с иконками. В любом из этих случаев обнулим сдвиг:

.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(); } );

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

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 );
    }
} );

И последнее: нам не нужно, чтобы меню пряталось, если мы кликнем где-нибудь в области самого меню. Так как мы привязываем события click/touchstart к документу (и меню в этом случае закрывается), мы должны сделать следующее:

this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );

А вот окончательный вариант функции _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();
}

И это все! Спасибо, сто прочли эту статью, надеюсь, вам понравился урок, и вы нашли его полезным!

Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: http://tympanus.net/codrops/2013/07/30/google-nexus-website-menu/
Перевел: Станислав Протасевич
Урок создан: 22 Марта 2014
Просмотров: 20662
Правила перепечатки


5 последних уроков рубрики "CSS"

^ Наверх ^