В этой публикации я хочу показать один из способов реализации адаптивного горизонтального меню с использованием Flexbox. Данный способ реализации меню используется на сайте Warface Hub, но немного с другой структурой и большим количеством свистелок.
Где-то с год назад, я попал в одну компанию, в которой мне сказали замечательную фразу: «Сначала делаем все с помощью CSS, а потом только добавляем JavaScript». Совет, вроде, хороший, и я ему последовал. Но как бывает, меня понесло. Сейчас мне это аукнулось тем, что не все нужно делать с таким подходом.
И так, ближе к делу. Я приступил к изучению и реализации.
Цели
- получить базовые навыки работы с Flexbox свойствами;
- разработать горизонтальное адаптивное меню;
- полученное решение применить в проекте.
Инструменты и документация
- NPM – в качестве менеджера пакетов (теперь активно переезжаем на Yarn)
- Grunt – инструмент, который поможет в сборке проекта
- Документация по Flexbox (см. Полезные ссылки);
- SASS
Вы можете использовать свой набор инструментов
Структура
Для организации структуры стилей для меню я пользовался концепцией, которая описана тут. Автор данной концепции предлагает разбить все описания стилей на несколько частей:
- layout– описывает положение компонентов и элементов на странице;
- component– описывает отображение и поведение элементов, которые входят в компонент;
- element– описывает отображение и поведение единичного элемента;
Таким образом мое понимание концепции привело меня к такой структуре:
- Base — описание констант, базовых стилей (как в normalize.css)
- Component — описание компонентов приложения. В нашем случае компонент «Menu»
- Element — описание стилей для элементов таких как кнопка, ссылка и т.п.
- Layout — описание расположения блоков на странице
- style.scss — в этом файле мы соберем все вместе
CSS и HTML теги input & label
Прежде чем начать рисовать HTML разметку, я бы хотел напомнить/показать интересное поведение CSS селекторов, которое нам пригодится.
<label for="input-0" class="label">Текст 1</label>
<input type="radio" name="input-trigger" id="input-0" class="input" />
<label for="input-1" class="label">Текст 1</label>
<input type="radio" name="input-trigger" id="input-1" class="input" />
В данном примере Вы можете заметить, что при нажатии на label Вы получите выбранный input. В этом ничего особенного нет (см. документацию), но самое интересное происходит со стороны CSS селекторов.
.input:checked {
border-color: red;
}
Данный CSS селектор будет обработан только тогда, когда будет выбран input (см. :checked)
Второй момент, на который нужно обратить внимание в CSS селекторах — это выбор следующего элемента (см. Adjacent sibling selectors и General sibling selectors). То есть мы можем выбрать следующий элемент после текущего.
.input:checked + .label {
color: red;
}
В этом примере мы получили следующее поведение: при выбранном элементе с классом input следующий за ним элемент с классом label будет изменен в соответствии с описанными стилями.
Теперь это все можно объединить воедино.
<input type="radio" name="menu-item-trigger" id="menu-close" class="input input-hidden">
<nav class="menu">
<input type="radio" name="menu-item-trigger" id="menu-item-0" class="input input-hidden" />
<div class="menu-item menu-item-trigger">
<label for="menu-close" class="menu-item-close"> </label>
<label for="menu-item-0" class="menu-item-label">
<i class="fa fa-home"></i>
<span class="menu-item-label-text">Menu Item 0</span>
</label>
<div class="menu-sub">
<li class="menu-item menu-item-sub">
<a href="#" class="menu-item-label">
<span class="menu-item-label-text">Sub Item Menu 0</span>
</a>
</li>
<li class="menu-item menu-item-sub">
<a href="#" class="menu-item-label">
<span class="menu-item-label-text">Sub Item Menu 1 - With long label</span>
</a>
</li>
<li class="menu-item menu-item-sub">
<a href="#" class="menu-item-label">
<span class="menu-item-label-text">Sub Item Menu 2 - Withtooolongwordslikeingerman</span>
</a>
</li>
</div>
</div>
</nav>
В данном примере я добавил несколько элементов input и label, чтобы получилось следующее поведение:
- Каждый элемент name=menu-item-trigger, кроме первого, в состоянии :checked будет изменять видимость и позиции последующих элементов label.menu-item-close и div.menu-sub таким образом, чтобы элемент label.menu-item-close полностью перекрывал элемент label.menu-item-label, а div.menu-sub отображался под элементом label.menu-item-label. То есть мы открываем подменю и меняем поведение при клике на основное меню;
- Первый элемент name=menu-item-trigger будет использован только для того, чтобы отменить все примененные изменения в предыдущем пункте, то есть закрыть подменю;
Не выбран ни один пункт меню:
Выбран один пункт меню:
После таких манипуляций остается только скрыть элементы input.
Flexbox
Теперь необходимо добавить стили, чтобы данное меню хорошо отображалось при различных разрешениях и различных браузерах. На текущий момент мы сосредоточили наши усилия на поддержке тех браузеров, которые больше всего используются посетителями нашего ресурса. Получился небольшой список: Chrome, Firefox, IE Edge, IE 11 и их мобильные варианты последних версий.
Поддержка осуществляется путем добавления префиксов (postcss) и отдельного написания стилей для конкретного браузера.
Адаптивность в Flexbox достигается очень просто. Достаточно описать контейнер, но иногда будет необходимо решить проблемы с контентом внутри. Например:
- элементы меню с длинными словами, как «knowledge base» и его немецкий перевод «Wissensdatenbank». В данном случае добавляется оборачивающий элемент для текста, к которому применяются примерно следующие стили:
.label-text { // @link: http://htmlbook.ru/css/text-overflow overflow: hidden; text-overflow: ellipsis; width: 100%; display: inline-block; }
- Картинки, которые нужно растянуть по ширине, но при задании width: 100%; они вылезают за пределы родительского блока. Тут поможет box-sizing: border-box; для этого элемента;
- Так же могут возникнуть проблемы с тем, что дочерние элементы не занимают всю возможную длину или не распределяются равномерно. Тут возможно поможет flex: 1 1 auto.
В данном примере контейнер для элементов описан так:
.menu {
display: flex;
align-items: center;
flex-wrap: wrap;
}
Для каждого элемента в контейнере необходимо задать стили так, чтобы он заполнял все возможное пространство и выравнивали контент внутри себя в центре по вертикали:
.menu-item {
flex: 1 1 auto;
display: flex;
flex-direction: column;
align-items: stretch;
}
Более красивого отображения меню можно достичь с помощью media queries и более точных размеров и позиций элементов.
Итог
После реализации данного примера я доработал его в рамках боевого проекта, который сейчас использует подобное адаптивное меню. Так же были выявлены плюсы и минусы избавления от Javascript в пользу CSS:
Плюсы:
- Не требуется ожидать загрузки JavaScript. Чаще всего меню находится в шапке сайта, поэтому эти стили можно положить в core.css, который описывает основные стили элементов, видимые пользователю при загрузке страницы;
- Меню будет работать, даже если в JavaScript произойдет что-то страшное и не будет инициализирован скрипт для меню.
Минусы:
- Ограниченные возможности CSS селекторов, например нельзя изменить родительский элемент при изменении дочернего;
- На iOS была замечена потеря производительности. Пришлось разбираться и проставлять will-change свойства;
- Нет возможности скрыть под меню через N секунд после потери фокуса (особенности реализации);
- Трудно разобраться в HTML разметке меню;
- Поддержка Flexbox в IE
на уровне «вырви глаз»
Полезные ссылки
- PCSS — описание концепции построения компонентного CSS;
- Guide To Flexbox (EN) — тут хорошо описаны свойства Flexbox;
- Guide To Flexbox (RUS) — тут хорошо описаны свойства Flexbox на русском языке;
- Mr. Froggy — поможет Вам овладеть навыками использования Flexbox
- Demo
- Github Project
Комментарии (12)
Andrey_Volk
14.12.2016 02:45Решение лаконичное (лаконичнее чекбоксов в данном примере ничего и не найти, наверное), но как насчет кроссбраузерности?
Veikedo
14.12.2016 06:22+1Вот каждый раз такой комент в статье про флексбокс.
Ну откройте вы http://caniuse.com/ и посмотрите, тем более что в минусах автор уже написал про ie.
OxCom
14.12.2016 10:55Как я указал в статье и как подметил Veikedo есть минусы с поддержкой в IE для версии младше 11.
Пример проблемы для IE10при большой вложенности блоков с display: flex; и различных свойствах таких как: flex-direction, flex, align-items и justify-content; периодически возникает ситуация, когда IE неправильно рассчитывает размеры и позиции блоков, что приводит к крайне неприятному отображению: элементы не на своих местах, большие расстояния между блоками, неправильное выравнивание, один блок занимает все пространство контейнера и вытесняет все остальные.tomgif
14.12.2016 10:26Интересное решение. Я обычно стараюсь избегать атрибута id из-за его специфичности, поэтому сам input оборачиваю в ещё один label.
Suliman
14.12.2016 10:27+1Когда кто-то пишет о том что label или input[type=radio] используется где-то в меню или в слайдере на css, а не в формах, сразу же закрываю такую статью… это же извращение полнейшее… есть разные типы тегов в html используйте их по назначению.
OxCom
14.12.2016 11:14+1Разработчику ничего не мешает применить такой подход к «правильным» HTML тегам.
Quilin
14.12.2016 12:24Просто вы счастливый человек, и с оперой мини не сталкивались, видимо.
Suliman
14.12.2016 16:12А как опера мини связана с неуместным использованием label и input[type=radio]? такой метод можно использовать только в случае если браузером не поддерживается js. никаких других причин нет так использовать теги.
Pinsky
14.12.2016 11:18В mincss можно подсмотреть как сделано меню-бутерборд(для низких разрешений экрана) без js.
xPomaHx
15.12.2016 10:42Крайне плохая практика. Однажды придется внести небольшое изменение которое не реализовать на css only и придется вообще все переделывать на js. Например в данном случае заказчик скажет, а я хочу чтобы меню открывалось по ховеру, а при клики заголовку меню чтобы оно закрывалось несмотря на то что на нем мышка.
В общем случае когда возникает вопрос как сделать на css или на js, просто посмотрите для чего то и то было придумано и будет вам ответ. А извращения подобные как в статье могут быть тока как спортивный интерес возможностей чистого ксс.
SbWereWolf
опечатка: «в рамках боЛевого проекта»