- PVSM.RU - https://www.pvsm.ru -

Адаптивное меню без Javascript

CSS меню без Javascript

В этой публикации я хочу показать один из способов реализации адаптивного горизонтального меню с использованием Flexbox. Данный способ реализации меню используется на сайте Warface Hub [1], но немного с другой структурой и большим количеством свистелок.

Где-то с год назад, я попал в одну компанию, в которой мне сказали замечательную фразу: «Сначала делаем все с помощью CSS, а потом только добавляем JavaScript». Совет, вроде, хороший, и я ему последовал. Но как бывает, меня понесло. Сейчас мне это аукнулось тем, что не все нужно делать с таким подходом.

И так, ближе к делу. Я приступил к изучению и реализации.

Цели

  1. получить базовые навыки работы с Flexbox свойствами;
  2. разработать горизонтальное адаптивное меню;
  3. полученное решение применить в проекте.

Инструменты и документация

  1. NPM [2] – в качестве менеджера пакетов (теперь активно переезжаем на Yarn)
  2. GruntJs [3] – инструмент, который поможет в сборке проекта
  3. Документация по Flexbox (см. Полезные ссылки);
  4. SASS [4]

Вы можете использовать свой набор инструментов

Структура

image

Для организации структуры стилей для меню я пользовался концепцией, которая описана тут [5]. Автор данной концепции предлагает разбить все описания стилей на несколько частей:

  • layout– описывает положение компонентов и элементов на странице;
  • component– описывает отображение и поведение элементов, которые входят в компонент;
  • element– описывает отображение и поведение единичного элемента;

Таким образом мое понимание концепции привело меня к такой структуре:

  • Base — описание констант, базовых стилей (как в normalize.css [6])
  • 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. В этом ничего особенного нет (см. документацию [7]), но самое интересное происходит со стороны CSS селекторов.

.input:checked {
    border-color: red;
}

Данный CSS селектор будет обработан только тогда, когда будет выбран input (см. :checked [8])

Второй момент, на который нужно обратить внимание в CSS селекторах — это выбор следующего элемента (см. Adjacent sibling selectors [9] и General sibling selectors [10]). То есть мы можем выбрать следующий элемент после текущего.

.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, чтобы получилось следующее поведение:

  1. Каждый элемент 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. То есть мы открываем подменю и меняем поведение при клике на основное меню;
  2. Первый элемент name=menu-item-trigger будет использован только для того, чтобы отменить все примененные изменения в предыдущем пункте, то есть закрыть подменю;

Не выбран ни один пункт меню:

Адаптивное меню без Javascript - 3
Выбран один пункт меню:

Адаптивное меню без Javascript - 4

После таких манипуляций остается только скрыть элементы input.

Flexbox

Теперь необходимо добавить стили, чтобы данное меню хорошо отображалось при различных разрешениях и различных браузерах. На текущий момент мы сосредоточили наши усилия на поддержке тех браузеров, которые больше всего используются посетителями нашего ресурса. Получился небольшой список: Chrome, Firefox, IE Edge, IE 11 и их мобильные варианты последних версий.

Поддержка осуществляется путем добавления префиксов (postcss [11]) и отдельного написания стилей для конкретного браузера.

Адаптивность в 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;
}

Адаптивное меню без Javascript - 5

Более красивого отображения меню можно достичь с помощью media queries [12] и более точных размеров и позиций элементов.

Итог

После реализации данного примера я доработал его в рамках болевого проекта, который сейчас использует подобное адаптивное меню. Так же были выявлены плюсы и минусы избавления от Javascript в пользу CSS:

Плюсы:

  1. Не требуется ожидать загрузки JavaScript. Чаще всего меню находится в шапке сайта, поэтому эти стили можно положить в core.css, который описывает основные стили элементов, видимые пользователю при загрузке страницы;
  2. Меню будет работать, даже если в JavaScript произойдет что-то страшное и не будет инициализирован скрипт для меню.

Минусы:

  1. Ограниченные возможности CSS селекторов, например нельзя изменить родительский элемент при изменении дочернего;
  2. На iOS была замечена потеря производительности. Пришлось разбираться и проставлять will-change [13] свойства;
  3. Нет возможности скрыть под меню через N секунд после потери фокуса (особенности реализации);
  4. Трудно разобраться в HTML разметке меню;
  5. Поддержка Flexbox в IE на уровне «вырви глаз»

Полезные ссылки

  • PCSS [5] — описание концепции построения компонентного CSS;
  • Guide To Flexbox (EN) [14] — тут хорошо описаны свойства Flexbox;
  • Guide To Flexbox (RUS) [15] — тут хорошо описаны свойства Flexbox на русском языке;
  • Mr. Froggy [16] — поможет Вам овладеть навыками использования Flexbox
  • Demo [17]
  • Github Project [18]

Автор: OxCom

Источник [19]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/html/220329

Ссылки в тексте:

[1] Warface Hub: https://www.warface.com

[2] NPM: https://www.npmjs.com/

[3] GruntJs: http://gruntjs.com/

[4] SASS: http://sass-lang.com/

[5] тут: https://github.com/dsheiko/pcss

[6] normalize.css: https://github.com/necolas/normalize.css/

[7] документацию: http://www.w3schools.com/tags/tag_label.asp

[8] :checked: https://developer.mozilla.org/en-US/docs/Web/CSS/:checked

[9] Adjacent sibling selectors: https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_selectors

[10] General sibling selectors: https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_selectors

[11] postcss: https://github.com/postcss/postcss

[12] media queries: https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries

[13] will-change: https://developer.mozilla.org/en-US/docs/Web/CSS/will-change

[14] Guide To Flexbox (EN): https://css-tricks.com/snippets/css/a-guide-to-flexbox/

[15] Guide To Flexbox (RUS): http://frontender.info/a-guide-to-flexbox/

[16] Mr. Froggy: http://flexboxfroggy.com/

[17] Demo: https://oxcom.github.io/pub-menu/

[18] Github Project: https://github.com/OxCom/pub-menu

[19] Источник: https://habrahabr.ru/post/317528/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox