Оценок: 1
551
Адаптивное горизонтальное меню для сайта на основе списка

Горизонтальное меню на основе списка

Здесь рассмотрим создание адаптивного меню, на основе списка. Для desktop версии меню будет горизонтальным, для мобильной вертикальным. Раскрытие самого меню и пунктов в мобильной версии через JQuery, все остальное - HTML и CSS.

Раскрытие будет плавным

Что бы было интереснее и полезнее (а еще потому, что сайт на чистом HTML сейчас большая редкость) добавим получение пунктов меню из БД, используя PHP, покажу весь процесс превращения данных из таблицы БД в меню сайта. Кому не интересно, можно просто пролистать дальше.

В самом конце есть рабочий пример

База данных

Все пункты меню изначально представлю в виде таблицы, подобно тому, как они хранятся в MySql (Один из вариантов).

Таблица будет называться "menu". Столбцы: menu_id (уникальный порядковый номер), name (название пункта меню), link (ссылка), parent (родитель)

menu_idnamelinkparent
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.

Оценок: 1

Комментарии (0)

    Надежный хостинг VPS серверов
    • Свои ISO образы
    • VDS с оплатой раз и навсегда
    • Аренда VDS на любой срок, с оплатой по дням
    • Большое разнообразие конфигураций
    • Дата-центры в ЕС и России
    + скидка 10%