Адаптивное меню средствами CSS

Далеко не во всех проектах уместны излишние эффекты, которые могут значительно нагружать страницы, а особенно, если это касается адаптивной верстки, где пользователь может просматривать сайт на смартфонах или планшетах. Большинство таких эффектов, создаются с использованием JavaScript, поэтому, для примера, я решил показать, как можно сделать один из важнейших элементов сайта, а именно меню навигации, практически не прибегая к JS, а в основном только средствами CSS. Совсем нельзя отказаться от использования JS по той причине, что на мобильных устройствах и обычных компьютерах или ноутбуках, многие события отличаются и, если на компьютере в CSS мы можем спокойно использовать свойство :hover, то на планшете это уже не сработает так, как нам бы этого хотелось. Самые нетерпеливые, могут сразу посмотреть результат (в примере изменяйте ширину окна браузера).

Итак, ставим такую задачу: создать резиновое по ширине меню, с переключением на мобильную его версию, где меню будет выпадающим по наведению или нажатию на кнопку. Структура HTML типичная для таких элементов, за исключением того, что мы добавляем еще один элемент - кнопку для раскрытия/скрытия меню в мобильной версии:

<header>
  <nav id="menu">
  <span id="nav_button">&equiv;</span>
    <ul>
      <li><a href="">Link 1</a></li>
      <li><a href="">Link 2</a></li>
      <li><a href="">Link 3</a></li>
      <li><a href="">Link 4</a></li>
      <li><a href="">Link 5</a></li>
    </ul>
  </nav>
</header>

CSS не совсем стандартный. Мы сделаем так, что наше меню будет вести себя так, как если бы мы использовали таблицу. Сразу хочу акцентировать внимание на том, что не все браузеры поддерживаю те свойства, которые мы будем использовать. Проблемы могут возникнуть с версиями IE ниже восьмой версии (хотя на них уже пора переставать ориентироваться) и в пару моментах есть маленькие сложности с IE8, но как решить их - напишу ниже.

* {
  margin: 0;
  padding: 0;
}
header {
  background-color: #C0C0C0;
}
#menu {
  display: table; /* описание ниже */
  width: 100%;
  border-collapse: collapse;
  -webkit-box-sizing: border-box; /* описание ниже */
  -moz-box-sizing: border-box; /* описание ниже */
  box-sizing: border-box; /* описание ниже */
}
#menu ul {
  display: table-row; /* описание ниже */
  background-color: #FFFFFF;
  list-style: none;
}
#menu ul li {
  display: table-cell; /* описание ниже */
  border: 1px solid #999999;
}
#menu ul li a {
  display: inline-block;
  width: 100%;
  height: 50px;
  line-height: 50px;
  font-size: 1.2em;
  text-align: center;
}
#menu ul li a:hover {
  background-color: #990000;
  color: #FFFFFF;
}
#nav_button {
  display: none;
  width: 50px;
  height: 50px;
  font-size: 2.5em;
  text-align: center;
  background-color: #FFFFFF;
  border: 1px solid #949494;
  cursor: pointer;
}
@media screen and (max-width: 600px) { /* описание ниже */
  #menu {
    display: inline-block;
    position: relative;
    width: auto;
  }
  #menu ul {
    display: none;
    position: absolute;
    top: 0;
    width: 100%;
    z-index: 20;
  }
  #menu ul li {
    display: list-item;
    border-top: none;
  }
  #nav_button {
    display: inline-block;
  }
  #menu:hover, #menu.open_nav {
    width: 100%;
    -webkit-user-select: none; /* описание ниже */
    -moz-user-select: none; /* описание ниже */
    -webkit-touch-callout: none; /* описание ниже */
  }
  #menu:hover ul, #menu.open_nav ul {
    display: block;
    margin-top: 50px;
  }
}

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

display: table;
Указывает, что элемент будет себя вести аналогично элементу <table>
display: table-row;
Элемент будет себя вести, как <tr>
display: table-cell;
Элемент будет себя вести, как <td> или <th>
box-sizing: border-box; (и с вендорными префиксами)
При расчетах ширины и высоты элемента, свойства width и height, будут включать в себя значения полей (padding) и границ (border). Нужно для того, чтоб избежать появления горизонтальной прокрутки, т.к. без этого свойства, при ширине меню в 100%, добавится еще и толщина border, а если есть, то и padding.
-webkit-user-select: none; и -moz-user-select: none;
Запрещает выделять элемент или текст. Нужно для мобильной версии, чтоб избежать неувязок, когда пользователь проводит по элементам меню, вместо того, чтобы нажать на него.
-webkit-touch-callout: none;
Отменяет всплытие подсказки при нажатии и удержании элемента. Работает только в Chrome и Safari. Так же, как и в предыдущем свойстве, служит для отмены нежелательных действий при работе с элементом.
@media (медиа-запрос)
Тема довольно обширная, но если в двух словах, то указывает браузеру, какие свойства применять для определенных типов носителей или технических характеристик устройства. В нашем случае - применять для screen (экранов мониторов) и max-width: 600px (если ширина окна браузера меньше 600px).

Самое интересное, что на этом можно было бы поставить точку, если бы мы не учитывали смартфоны, планшеты и с уверенностью сказать, что меню сделано полностью на HTML и CSS. Но придётся прибегнуть к помощи JS/jQuery, а в данном случае, раз уж решено было сделать с наименьшей нагрузкой, именно к чистому JavaScript.

var d = document,
    navBut = d.getElementById('nav_button'), // кнопка включение меню
    navMenu = d.getElementById('menu'); // выпадающее меню
// функция определения наличия родительского элемента по ID
function hasParent(el, sId){
  while(el) {
    if (el.id == sId) return true;
    el = el.parentNode;
  }
  return false;
}
// по касанию на кнопке, добавляем класс менюшке, 
// который соответствует свойству в css "#menu:hover"
// и разворачиваем меню
navBut.addEventListener('touchstart', function(e) {
  e.preventDefault();
  navMenu.classList.add('open_nav');
}, false);
// по касанию в документе, 
// если событие не было на каком-нибудь элементе меню (определяем с помощью функции "hasParent"),
// убираем класс "open_nav" из меню, что привод к его закрытию
d.addEventListener('touchstart', function(e) {
  if(!hasParent(e.target, 'menu')) navMenu.classList.remove('open_nav');
}, false);

Записываем этот код в отдельный js-файл и подключаем его в самом низу страницы, перед закрывающим тегом </body>. Теперь пару слов о кроссбраузерности... Если вы рассчитываете на Internet Explorer ниже девятой версии, то для корректной работы, нужно подключить два фиксящих скрипта внутри тега <head>:



Первый позволяет старым браузерам адекватно воспринимать теги HTML5, а второй - "медиа-запросы", с которыми они так же не дружат. Хотя, с другой стороны, такие браузеры не используются на мобильных устройствах, а тот же тег <nav> можно заменить на <div>. Поэтому использовать эти скрипты или нет - дело, конечно же, ваше личное, но я их считаю избыточными. Вот теперь меню готово к использованию на любых устройствах.


Очень часто, особенно новички, в погоне за мнимой красочностью сайта, переполняют его лишней графикой, скриптами, которые выполняя какое-то маленькое действие, весят порой больше чем вся страница. Не впадайте в крайности и задумывайтесь о тех пользователях, которые могут заходить к вам не только с разных устройств, но еще и по низкоскоростному интернет-соединению. Успехов! ;)

Incode Pro logo

19 комментариев

Гость 23.09.2015 21:47
а можно это все в html 4 использовать? какие нужно в код изменения внести?
Incode 24.09.2015 00:16
а можно это все в html 4 использовать?
Честно говоря, как-то странно вопрос звучит. Замените теги из HTML5 (header, nav) на какие-нибудь другие (хоть те же div) и вот вам HTML4. Но что-то мне подсказывает, что вы имели в виду другое. Если это так, то уточните вопрос, конкретизируйте его.
vaco 03.03.2016 22:17
@Incode, помогите пожалуйста внедрить в css подменю?
<ul>
  <li><a href="">Link 1</a></li>
  <li><a href="">Link 2</a>
    <ul>
      <li><a href="">SubLink 1</a></li>
      <li><a href="">SubLink 2</a></li>
    </ul>
  </li>
  <li><a href="">Link 3</a></li>
  <li><a href="">Link 4</a></li>
  <li><a href="">Link 5</a></li>
</ul>
vaco 03.03.2016 22:18
блин, </li> улетел, но смысл вопроса правильный))
Incode 04.03.2016 00:53
@vaco, попробовать внедрить можно, но я сторонник того, что к какой-то определенной и тем более специфической задаче, должен быть свой подход. На скорую руку получился такой вариант
vaco 04.03.2016 14:03
@Incode, то что надо, спасибо! Отличный пример!
Гость 21.07.2016 12:46
@Incode, спасибо, я новичок и смог адаптировать свое меню у себя на сайте, но, подскажите, при открытии вкладки sublink, у вас всё остальное меню сдвигается, а у меня нет. В чем может быть причина? site:leonsvet.ru
Incode 21.07.2016 15:25
остальное меню сдвигается
Я не очень понимаю о каком "остальном меню" вы говорите и куда оно сдвигается. Если вы про менюшку в сайдбаре моего блога, а не про пример в этой статье, то я его писал сам, без всяких Bootstrap, который используете вы или каких-то еще непонятных мне фреймворков. И почему у вас меню не сдвигается - так может быть вы эту функцию у себя просто не реализовали? )) И кстати, обратите внимание на ошибки в консоли. Возможно, что причина кроется в них.
Гость 17.08.2016 12:22
В каком месте тут CSS3 если выпадает оно с помощью JS?
Incode 17.08.2016 14:15
В каком месте тут CSS3 если выпадает оно с помощью JS?
Во-первых, вы внимательно читали то, что написано в статье или просто заметили JS-код и решили что-нибудь возразить? Думаю, что второе. А во-вторых, можете отключить JS и попробовать: будет выпадать меню или нет.
Гость 21.08.2016 03:40
ncode 21.07.2016 15:25
остальное меню сдвигается
Я не очень понимаю о каком "остальном меню" вы говорите и куда оно сдвигается. Если вы про менюшку в сайдбаре моего блога, а не про пример в этой статье, то я его писал сам, без всяких Bootstrap, который используете вы или каких-то еще непонятных мне фреймворков. И почему у вас меню не сдвигается - так может быть вы эту функцию у себя просто не реализовали? )) И кстати, обратите внимание на ошибки в консоли. Возможно, что причина кроется в них.
я использую бутстрап только в одном месте, и то не относящемся к меню...сейчас настроил более менее правильно, но она у меня не скрывается, если нажать на свободное от меню место, подскажите, в какой параметр за это отвечает?
Гость 21.08.2016 03:56
+ на айфоне меню открывается хорошо, но с планшета самсунг таб2 почему то при нажатии на значок меню - сразу открывается первая вкладка(Link1), как будто задевает за нее... и в вашем демо и на моем сайте тоже...
то есть открыть и посмотреть список ссылок не получается, сразу же кликает по первой ссылке, как будто кликает два раза... это стандартный браузер
Incode 22.08.2016 00:23
я использую бутстрап
Тогда не очень понятно, зачем вам это меню, если в бутстрап это уже реализовано?
но с планшета самсунг таб2 ... сразу открывается первая вкладка
На планшете проверить смогу только завтра. На смартфоне Samsung проверил и таких багов нет.
Гость 22.08.2016 02:13
@Incode, на смартфоре s5 тоже самое... может от прошивки зависит? или версией браузера? и как тогда бороться с этим?
Гость 23.08.2016 18:47
Пробовали на телефоне? @Incode,
Incode 23.08.2016 19:52
Пробовали на телефоне?
Да, попробовал на родном браузере андроида и баг такой наблюдается. Для выпадающего меню нужно сделать отступ на высоту кнопки. В css после 75-ой строки допишите свойство margin-top: 50px;
@media screen and (max-width: 600px) {
    /*
    ... остальные стили
    */
    #menu:hover ul, #menu.open_nav ul {
        display: block;
        margin-top: 50px;
    }
}
Гость 23.08.2016 22:36
@Incode, это вариант обойти эту проблему, а как решить? ведь если меню состоит из подменю - то проблема вновь станет актуальной
Incode 24.08.2016 03:12
это вариант обойти эту проблему, а как решить?
Это и есть решение. Точнее сказать - одно из решений. Всё дело в том, что в разных браузерах, некоторые события по разному взаимодействую с DOM-объектами. Это достаточно часто встречается и на десктопах, а на мобильные устройствах - так сплошь и рядом.
если меню состоит из подменю
Смотрите пример с подменю. Положил его под пункт "Link 3". Все изменения прописал в самом документе (css в шапке, js в подвале), за исключением одной поправки в css-файле. В медиазапросе заменил:
#menu:hover ul {
}
/* на */
#menu:hover > ul {
}
Хоть это и не столь важно для мобильных устройств, но, как говорится, для чистоты эксперимента.
В принципе, весь механизм, отвечающий за раскрытие/скрытие меню, можно переложить на JS, но если есть возможность, то я предпочитаю CSS.
kolver 22.10.2017 00:35
Почему 1 меню не работает, то есть даже не подсвечивается и не кликабельна?
Ваш комментарий:
X