Здесь рассмотрим создание адаптивного меню, на основе списка. Для desktop версии меню будет горизонтальным, для мобильной вертикальным. Раскрытие самого меню и пунктов в мобильной версии через JQuery, все остальное - HTML и CSS.
Раскрытие будет плавным
Что бы было интереснее и полезнее (а еще потому, что сайт на чистом HTML сейчас большая редкость) добавим получение пунктов меню из БД, используя PHP, покажу весь процесс превращения данных из таблицы БД в меню сайта. Кому не интересно, можно просто пролистать дальше.
В самом конце есть рабочий пример
База данных
Все пункты меню изначально представлю в виде таблицы, подобно тому, как они хранятся в MySql (Один из вариантов).
Таблица будет называться "menu". Столбцы: menu_id (уникальный порядковый номер), name (название пункта меню), link (ссылка), parent (родитель)
menu_id | name | link | parent |
0 | Пункт 1 | # | 0 |
1 | Пункт 2 | # | 0 |
2 | Пункт 3 | # | 0 |
3 | Пункт 4 | # | 0 |
4 | Подпункт 1 | # | 1 |
5 | Подпункт 2 | # | 1 |
6 | Подпункт 3 | # | 1 |
7 | Подпункт 4 | # | 3 |
8 | Подпункт 5 | # | 3 |
9 | Подпункт 6 | # | 3 |
10 | Подпункт 7 | # | 3 |
11 | Подпункт 8 | # | 3 |
А теперь займемся выводом.
PHP. Получение пунктов, подготовка к выводу в шаблон
Т.к. цель - получить меню на сайте, а не научится работать с БД MySQL, здесь немного упрощу, иначе нужно будет начинать с подключения к MySQL и оттуда двигаться дальше, а учитывая что здесь могут быть варианты, рискую запутать.
Для начала запрос к БД для получения пунктов, который будет находится в функции getMenu($parent) {}. Именно к фунции с таким именем будем дальше обращаться, функция будет получать родителя в переменной "$parent" и возвращать массив с пунктами. Модель, MySQL запрос:
SELECT * FROM menu WHERE `parent` = '" . (int)$parent . "'
Получаем не сразу все возможные пункты, а по родителю, т.е. сначала корневые, затем перебираем их и для каждого корневого получаем подпункты. Это выглядит так (контроллер):
$menu = array();//для начала объявим переменную с пустым массивом, на случай если в таблице ничего не найдется.
$q_menu = getMenu(0); //получаем массив с корневыми пунктами и затем перебираем его
foreach ($q_menu as $menu_item) {
$childs = array(); //пустой массив с подпунктами, опять же на случай, если таковых нет.
$q_childs = getMenu($menu_item['menu_id']);//Получаем подпункты для текущего пункта меню. Здесь текущий пункт - родитель. Перебираем их.
foreach ($q_childs as $child_item) {
//формируем массив с подпунктами
$childs[] = array(
'menu_id' => $child_item['menu_id'],
'name' => $child_item['name'],
'link' => $child_item['link']
);
}
//Теперь формируем массив с корневыми пунктами, включающий массивы подпунктом (многомерный массив всего меню)
$menu[] = array(
'menu_id' => $child_item['menu_id'],
'name' => $child_item['name'],
'link' => $child_item['link'],
'childs' => $childs
);
}
Теперь можно переходить к выводу в шаблон
HTML. Вывод в шаблоне. И немного PHP
Для начала вывод с помощью PHP
<span class="menu-toggle"><img src="images/menu/menu40.png" alt="Показать меню" /></span><nav id="top-menu" class="top-menu">
<ul class="parent">
<?php foreach ($menu as $menu_item) { ?>
<?php if ($menu_item['childs']) { ?>
<li><a href="<?php echo $menu_item['link']; ?>"><?php echo $menu_item['name']; ?></a>
<span class="togg"></span>
<div class="child">
<ul>
<?php foreach ($menu_item['childs'] as $child_item) { ?>
<li><a href="<?php echo $child_item['link']; ?>"><?php echo $child_item['name']; ?></a></li>
<?php } ?>
</ul>
</div>
</li>
<?php } else { ?>
<li><a href="<?php echo $menu_item['link']; ?>"><?php echo $menu_item['name']; ?></a></li>
<?php } ?>
<?php } ?>
</ul>
</nav>
В результате получим вот такой HTML:
<span class="menu-toggle"><svg viewBox="0 0 448 512" width="100" title="bars"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z" /></svg></span>
<nav id="top-menu" class="top-menu">
<ul class="parent">
<li><a href="#">Пункт 1</a>
<span class="togg"></span>
<div class="child">
<ul>
<li><a href="#">Подпункт 1</a></li>
<li><a href="#">Подпункт 2</a></li>
<li><a href="#">Подпункт 3</a></li>
</ul>
</div>
</li>
<li><a href="#">Пункт 2</a></li>
<li><a href="#">Пункт 3</a>
<span class="togg"></span>
<div class="child">
<ul>
<li><a href="#">Подпункт 4</a></li>
<li><a href="#">Подпункт 5</a></li>
<li><a href="#">Подпункт 6</a></li>
<li><a href="#">Подпункт 7</a></li>
<li><a href="#">Подпункт 8</a></li>
</ul>
</div>
</li>
<li><a href="#">Пункт 4</a></li>
</ul>
</nav>
Подпункты обернуты сначала в контейнер .child, затем в ul. Сделано так, что бы визуально сделать отступ между основным меню и всплывающими пунктами при наведении.
Тепеерь займемся оформлением и для начала напишем небольшой скрипт для раскрытия меню, проверим что меню скрывается и раскрывается, затем CSS.
Оформление JS и CSS
Скрипт jQuery открытия/закрытия меню:
$(".menu-toggle").click(function () {
$('#top-menu').toggle().toggleClass('open');
$('body').toggleClass('menuopen')
});
/*Для раскрытия внутренних пунктов, мобильное меню*/
$('.togg').click(function () {
if ($(this).hasClass('active')) {
$(this).removeClass('active').next().removeClass('open');
} else {
$(this).addClass('active').next().addClass('open');
}
});
Добавление класса "menuopen" для body нужно, что бы фиксировать страницу при раскрытии меню в мобильной версии (прокручиваться должно только меню, а не вся страница).
Без применения каких-либо стилей элементы меню будут расположены друг под другом, все подпункты раскрыты, соответственно необходимо добавить некоторые CSS стили.
/*Скроем кнопку для desktop*/
.menu-toggle {display:none;}
.top-menu ul {
list-style-type: none;
padding-left: 0px;
margin: 0px;
}
.top-menu ul li {position: relative;}
.top-menu .child {
background: rgba(255, 255, 255, 0.4);
border-radius:0px 0px 5px 5px;
/*Следующим способом можно добиться плавного раскрытия списка.
Ограничить высоту = 0 в неактивном состоянии и максимально возможным значением в активном.
Так же нужно добавить плавный переход (ниже)*/
max-height: 0px;
overflow: hidden;
}
.top-menu .child ul {
background: rgba(255, 255, 255, 0.7);
min-width: 220px;
box-shadow: 0px 2px 5px #a7a7a7;
border-radius: 0px 0px 5px 5px;
margin:10px 4px 4px;
}
.top-menu a {
display: block;padding: 5px 15px;color:#000;
text-decoration:none;
}
.top-menu .togg {position: absolute;bottom: -8px;left: 50%;margin-left: -2px;}
.top-menu .togg:before {border-top: 4px solid #f44242;border-left: 4px solid transparent;border-right: 4px solid transparent;content: '';display: block;}
.top-menu .togg.active {transform: rotate(180deg);}
/*Добавим плавный переход для некоторых элементов*/
.togg, a, ul,.top-menu .child {transition: all 0.5s ease 0s;-moz-transition: all 0.5s ease 0s;-webkit-transition: all 0.5s ease 0s;-o-transition: all 0.5s ease 0s;}
@media (min-width: 1200px) {
/*Делаем горизонтальным. Альтернативный вариант - .parent {display:flex}*/
.top-menu .parent > li {float: left;}
/*Показываем подпункты по наведению (В мобильной версии будет по клику)*/
.top-menu li:hover .child {max-height:1000px;}
.top-menu ul:after {display:block;content:'';clear:both;}
.top-menu .child {position: absolute;top: 100%;left: 0px;}
/*Что бы активную стрелку сделать ярче. Для мобильного не нужно*/
.top-menu li > .togg:before {opacity:0.5}
/*Поворот стрелки, отдельно при наведении, что бы не мешало повороту при нажатии*/
.top-menu li:hover > .togg {transform: rotate(180deg);z-index:10}
.top-menu li:hover > .togg:before {opacity: 1;}
/*Выделение цветом при наведении*/
.top-menu li:hover > a {color: #d60400}
}
@media (max-width: 1199px) {
/*Покажем кнопку и спрячем меню*/
.menu-toggle {display:block;}
.top-menu {
display:none;
position:absolute;
left:0;right:0
/*далее ограничу высоту меню и добавлю скрол*/
height:80%;overflow:auto;
}
.menu-toggle svg {
padding: 5px;background:rgba(255,255,255,0.5);
border-radius: 5px;width: 40px;
}
/*Зафиксируем страницу при раскрытом меню*/
body.menuopen {width: 100%;height: 100%;overflow: hidden;}
/*Подпункты раскрываются по клику на span, что бы было меньше шансов попасть по ссылке, не будем делать ее на всю ширину*/
.top-menu a {display:inline-block;}
.top-menu .parent > li {
border-bottom: 1px dotted #ccc;
min-height: 36px;
display: flex;flex-wrap:wrap;
align-items: center;
}
/*Растяну на всю ширину .child, т.к. находится внутри flex*/
.top-menu .child {width:100%}
/*Здесь аналогично, как и для desktop*/
.top-menu .child.open {max-height:1000px}
.top-menu .togg {
height: 30px;width: 30px;top: 3px;border: 1px solid #5e5e5e;
bottom: auto;right: 10px;left: auto;margin-left: 0px;padding: 10px 5px;
box-sizing: border-box;border-radius: 30px;
}
.top-menu .togg:before {
border-top: 10px solid #ec3437;
border-left: 9px solid transparent;
border-right: 9px solid transparent;
}
}
Результат
Здесь изменил размер окна для перехода к мобильному меню, что бы можно было увидеть мобильное и десктоп в окне codepen.
Есть и некоторые другие отличия, мелкие, в CSS
See the Pen Пример меню by Vladimir (@webrazrab_ru) on CodePen.