Доброго времени суток. Сегодня разберем создание “двухуровневого” меню. Нам пригодятся знания, полученные из первой части.
Первый уровень ничем не отличается от одноуровневого меню. На данный момент самым популярным методом является float:left. Вскоре, я думаю, все перейдут на flexbox, а пока этого не произошло будем использовать float.
В статье | Все статьи |
|
Начинаем, как обычно с разметки
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<div class="menu_top clearfix"> <ul> <li><a href="">Category 01</a> <ul> <li><a href="">Product 01 - 1</a></li> <li><a href="">Product 01 - 2</a></li> <li><a href="">Product 01 - 3</a></li> </ul> </li> <li><a href="">Category 02</a> <ul> <li><a href="">Product 02 - 1</a></li> <li><a href="">Product 02 - 2</a></li> <li><a href="">Product 02 - 3</a></li> <li><a href="">Product 02 - 4</a></li> </ul> </li> <li><a href="">Category 03</a> <ul> <li><a href="">Product 03 - 1</a></li> <li><a href="">Product 03 - 2</a></li> </ul> </li> <li><a href="">Category 04</a> <ul> <li><a href="">Product 04 - 1</a></li> <li><a href="">Product 04 - 2</a></li> <li><a href="">Product 04 - 3</a></li> <li><a href="">Product 04 - 4</a></li> <li><a href="">Product 04 - 5</a></li> </ul> </li> <li><a href="">Category 05</a></li> <li><a href="">Category 06</a></li> <li><a href="">Category 07</a> <ul> <li><a href="">Product 07 - 1</a></li> <li><a href="">Product 07 - 2</a></li> </ul> </li> <li><a href="">Category 08</a></li> <li><a href="">Category 09</a></li> </ul> </div> |
Как вы можете заметить 2-ой уровень лежит на том же уровне что и <a>. В интернете Вы можете найти более экзотические варианты (обычно это в старых статьях). Сейчас такая разметка используется де-факто, поэтому и я буду придерживаться ее.
И так, 1-ый уровень мы можем оформить 3 вариантами, 2-ой тоже :). Итого у нас появляется комбинация минимум из 9 вариантов. Какой использовать решать Вам. Способ оформления для 1-ого уровня мы уже определили (float:left). Переходим к вариантам 2-го:
left:-9999px
(example_1::github)
Не скажу, что самый популярный способ, но имеет довольно широкое распространения (пока не знаю почему).
Суть его заключается в том, что 2-ому уровню (li > ul) присваивается свойство position:absolute (это свойство используется во всех случаях, т.к. необходимо, чтобы 2-ой уровень был выдернут из основного потока контекста) и закидывается за границы экрана, т.е. left:-9999px (с тем же успехом можно использовать и right/top:-9999px, или bottom:9999px).
А при наведении на родительский li (li:hover > ul), присвоить left:0 таким образом меню вернется на свое место. Примерно так.
Теоретически все классно, но если вы так сделаете, то появится одна проблемка: все меню 2-го уровня будут появлятся в левом верхнем углу экрана. А случилось это потому, что относительные координаты
Для позиционированного элемента определяет расстояние от левого края родительского элемента, не включая отступ, поле и ширину рамки, до левого края дочернего элемента. Отсчет координат зависит от значения свойства position. Если оно равно absolute, в качестве родителя выступает окно браузера и положение элемента определяется от его левого края
Или первый ближайший предок с position:relative
Т.е. нам надо присвоить position:relative первому уровню (.menu_top > ul > li)
А если мы это сделаем, то и ширина всего 2-го уровня будет наследоваться от элемента 1-го уровня. И если мы захотим ее изменить, то придется задавать min-width или width.
Короче куча проблем.
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
.menu_top > ul > li { /* 1-ый уровень */ float: left; padding: 5px; border: 1px black solid; background: #fffa6e; position: relative; /* Чтобы left:0 не отображал наше подменю в левом крае страницы, а сразу под 1 уровнем */ } .menu_top > ul > li > ul { /* 2-ой уровень */ z-index: 1; /* Т.к. мы используем position:relative на li, то элементы накладывается (если меню в 2 строки) */ left: -9999px; /* Убираем подменю куда подальше*/ position: absolute; /* Если не будет absolute, то все элементы будут строиться внутри существующего */ background: #f2ffe8; } .menu_top > ul > li:hover > ul { /* при наведении на li 1-го уровня отобразить меню 2-го уровня */ left: 0; /* То свойство из-за которого появляется куча проблем */ } |
display:none
(example_2::github)
Все можно сделать проще и без танцев с бубном. Для этого вместо left:-9999px; мы указываем display:none, а вместо left:0 -> display:block. И все
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.menu_top > ul > li { /* 1-ый уровень */ float: left; padding: 5px; border: 1px black solid; } .menu_top > ul > li > ul { /* 2-ой уровень */ display: none; /* скрываем подменю */ position: absolute; /* Если не будет absolute, то все элементы будут строиться внутри существующего*/ } .menu_top > ul > li:hover > ul { display: block; /* подменю появляется */ } |
Вроде бы этот метод лишен всех предыдущих проблем (и это действительно так), но он тянет свои собственные. Обратите внимание, где появляется 2-ой уровень. Т.к. он находится на одном уровне с <a>, то и получает, что на него действуют все padding предназначенные для li. И тут открывается целое поле для творчества. Можно:
- Оставить все как есть
- Не использовать padding
- margin-left: -50% (CSS2.1). В этом случае меню 2-ого уровня будет прилеплено к границе родительского элемента.
- transform: translateX(-50%). А это свойство позволяет нам отцентровать меню под родителем.
А вот как это выглядит в реальности
Комбинация
(example_3::github)
Думаю многих заинтересует как сделать так, чтобы каждый пункт меню писался в 1 строку. Т.к. если мы используем процентные margin, left приходится задавать position:relative для родителей, что в свою очередь влияет на width дочерних элементов. Я нашел следующий способ: свойство white-space: nowrap;
P.S. заметьте, эти решения используются исключительно в тех случаях, если вы не хотите задавать фиксированную ширину меню. Последнии 2 способа имеют смысл если вы не задаете фиксированную ширину подменю.
visibility:hidden + opacity: 0
(example_4::github)
По большому счету можно использовать visibility как еще один способ, но смысла в этом нет. Т.к. при условии, что мы используем position:absolute разницы с display:none не будет (только уменьшение производительности, за счет того, что меню остается в контекста и браузер будет рассчитывать его). Но у visibility есть одно существенное преимущество. Его можно использовать вместе со свойством opacity. Оно необходимо, в том случае, если Вы хотите использовать CSS-анимацию появления:
- opacity:0 – меню полностью прозрачное, но на него до сих пор можно нажать.
- visibility:hidden – прячем меню (теперь на него нажать нельзя)
- при наведении (li:hover > ul). Делаем меню полностью НЕпрозразным (opacity: 1) и видимым (visibility:visible)
- Добавляем анимацию transition: opacity 1s easy-out;
- Наслаждаемся
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.menu_top > ul > li > ul { /* 2-ой уровень */ visibility: hidden; /* скрываем подменю */ opacity: 0; /* NEW */ position: absolute; /* Если не будет absolute, то все элементы будут строиться внутри существующего*/ background: #f2ffe8; transition: opacity 1s ease-out; /* То, ради чего мы все это затеяли: CSS анимация */ } .menu_top > ul > li:hover > ul { visibility: visible; /* подменю появляется */ opacity: 1; /* NEW */ } |