Выпадающее меню на CSS
2006-03-30 перейти к комментариям

Про выпадающие меню на CSS написано много статей, взять хотя бы Suckerfish Dropdowns, чью методику я хочу немного развить.
Данная статья предназначена для новичков в CSS, желающих научиться делать красивые меню без использования JavaScript, сохраняя при этом чистый код HTML-страниц. Я постараюсь объяснить каждое применяемое правило, показывая промежуточные результаты.
Вот к чему мы придем: финальное меню.
Разметка
Начнем мы с семантической разметки. Наше меню, содержащее сразу три уровня — это простой ненумерованный список, а каждое подменю в нем — это вложенный список. Такой подход имеет ряд преимуществ:
- Код вашего меню занимает мало места
- Меню становится доступным для поисковиков и альтернативных клиентов
- Вы разделяете содержимое и представление, контролируя оформление только при помощи CSS
В HTML это выглядит примерно так:
<ul id="nav">
<li><a href="#null">Домой</a></li>
<li><a href="#null">Каталог</a>
<ul>
<li><a href="#null">Вся продукция</a>
<ul>
<li><a href="#null">По дате</a></li>
<li><a href="#null">Производители</a></li>
<li><a href="#null">Другое</a></li>
</ul>
</li>
</ul>
</li>
...
</ul>
Пусть вас не пугает вложенность списков. Главное — следить за правильностью открытия/закрытия тегов. В частности, каждый вложенный тег <ul> должен содержаться внутри тега <li>.
Вот что мы пока имеем: шаг 1.
Немного стиля
Теперь мы добавим несколько кусочков CSS в наш код:
#nav, #nav ul {
list-style: none;
margin: 0;
padding: 0;
border: 1px solid #000;
background: #515151;
float: left;
width: 100%;
}
#nav li {
float: left;
position: relative;
background: #515151;
back\ground: none;
}
#nav li ul {
display: none;
}
Этими тремя правилами мы сделали следующие вещи:
- Убрали буллеты из нашего списка —
list-style:none - Обнулили отступы
paddingиmarginу элементов меню - Украсили меню границей и задним фоном. Свойство
back\ground:noneслужит для задания прозрачного фона во всех браузерах кроме IE 5. Я поясню это позднее - Заставили каждый элемент списка
<li>, встать на одну линию при помощи правилаfloat:left - Скрыли подменю 2-го и 3-го уровня, указав
display:none
Когда блочные элементы имеют свойство float, они становятся «плавающими». Это позволяет выстраивать их в одну линию друг за другом: ? ? ? ? ?. Подробнее про свойство float.
Так как все элементы списка <ul id="nav"> теперь «плавающие», то сам список «схлопывается». Это происходит из-за невозможности вычислить реальную высоту элемента, который содержит другие «плавающие» элементы.
Для борьбы с этой напастью существует несколько методов, однако они могут не работать в новом IE 7. Поэтому здесь я решил использовать метод FNE, заключающийся в присвоении свойства float:left самому контейнеру. Это избавляет нас от «схлопывания» списка, но заставляет нижележащие элементы обтекать меню справа. Именно поэтому мы указываем ширину для всего меню 100% — чтобы справа просто не оставалось места.
Кроме того, мы могли указать свойство clear:both для элемента, следующего сразу за меню. Это заставило бы его опуститься ниже всех «плавающих» элементов.
Что мы получили: шаг 2.
Добавим немного оформления нашим ссылкам:
#nav a {
color: #fff;
text-decoration: none;
display: block;
width: 120px;
padding: 4px 10px;
background: url(dot.png) repeat-y right;
}
#nav a:hover {
color: #000;
background: #ccc;
}
#nav li:hover {
background: #333;
}
Первым правилом мы оформили ссылки (чтобы они больше походили на кнопки):
- Каждому элементу
<a>мы присвоили свойствоdisplay:block, что дало нашим ссылкам ширину и высоту - Убрали подчеркивание при помощи
text-decoration:none - Задали ширину каждой ссылки 120 пикселей (справедливости ради, надо сказать, что реальная ширина равна 140 (120 + 10 + 10) пикселям, так как в нее включаются и отступы)
Задание ширины является необходимостью при использовании свойства float для всех элементов кроме элемента <img>.
Селектор #nav a:hover срабатывает в том случае, когда мы подводим курсор к ссылке, а #nav li:hover — когда подводим его к элементу списка. Второй случай понадобиться нам для того, чтобы в меню оставался «след» наших перемещений (мы это увидим далее).
Промежуточный результат: шаг 3.
Мы же не зря назвали это меню «выпадающим»
Мы дописываем к уже существующему правилу:
#nav li ul {
display: none;
}
следующие инструкции:
#nav li ul {
display: none;
position: absolute;
background: url(fone-tr.png);
padding: 8px 0;
width: 138px;
}
Выражение position:absolute служит для абсолютного позиционирования подменю относительно элемента <li> верхнего уровня.
Когда мы имеем родительский элемент, позиционированный «относительно» (т. е. имеющий position:relative), все позиционированные абсолютно элементы, содержащиеся в нем, будут позиционироваться относительно родительского элемента, а не относительно всей страницы.
#nav li li a {
width: 118px;
background: none;
}
#nav li:hover ul {
display: block;
}
Вся магия выпадающего меню заключена в строке display:block для #nav li:hover ul. Именно она заставляет подменю «появиться» при подводе курсора к ссылке, сменяя ранее установленный режим display:none:
#nav li:hover li ul {
display: none;
width: 138px;
top: -9px;
left: 133px;
}
#nav li:hover li:hover ul {
display: block;
}
Ширина нашего подменю равна 138 пикселям из-за того, что мы вычитаем 2 пикселя от границ с каждой стороны: 140 – 1 – 1 = 138 пикселей.
Селектор #nav li:hover li ul оказывает влияние на подменю 3-го уровня. Мы его сдвигаем влево на ширину 133 пикселя (величина чисто эмпирическая), а также немного вверх (чтобы оно оказалось на одном уровне с активной ссылкой). Теперь, при наведении мышки, наше подменю будет выскакивать справа от ссылки.
Выпадающее меню (пока не для IE): шаг 4.
Фактор IE
Жизнь многих веб-разработчиков стала бы проще если бы не было Internet Explorer. Ситуация начинает улучшаться в связи с выходом седьмой версии, но до повсеместного ее распространения еще очень далеко.
В ранних версиях IE псевдокласс hover поддерживается только для элемента <a>. В нашем же случае это требуется для элемента списка <li>. Поэтому мы будем использовать простую функцию JavaScript для нужной нам реакции на подведение мышки:
<script type="text/javascript">
jsHover = function() {
var hEls = document.getElementById("nav").getElementsByTagName("LI");
for (var i=0, len=hEls.length; i<len; i++) {
hEls[i].onmouseover=function() { this.className+=" jshover"; }
hEls[i].onmouseout=function() { this.className=this.className.replace(" jshover", ""); }
}
}
if (window.attachEvent && navigator.userAgent.indexOf("Opera")==-1) window.attachEvent("onload", jsHover);
</script>
Это позволяет «прицепить» класс jshover к любому элементу <li>, над которым проходит курсор. Эта функция работает только в Internet Explorer — для других браузеров она просто не нужна.
Теперь, чтобы меню заработало в IE, добавим к четырем уже существующим правилам по дополнительному селектору li.jshover:
#nav li:hover,
#nav li.jshover {
...
}
#nav li:hover ul,
#nav li.jshover ul {
...
}
#nav li:hover li ul,
#nav li.jshover li ul {
...
}
#nav li:hover li:hover ul,
#nav li.jshover li.jshover ul {
...
}
Теперь можете смотреть и в IE: шаг 5, финальный.
Дополнительная информация
В качестве фона для подменю используется полупрозрачный PNG-файл. IE 6 не поддерживает полупрозрачность, но вы можете это исправить.
Из-за использования полупрозрачной картинки мне пришлось убрать цвет фона. Это привело к тому, что при отключенных картинках буквы подменю становятся не видны. Выход один: задать цвет фона для #nav li, потеряв при этом полупрозрачность.
Я использую хак back\ground:none;, чтобы принудительно задать цвет меню для IE 5. Если этого не сделать, то в этом браузере фон не отображается. Наверное это можно исправить как-то по-другому, но у меня нет желания разбираться со всеми его причудами.
UPD. akella подсказал, что при некоторых настройках системы меню может распираться в Опере. Это происходит из-за использования для всех размеров «абсолютных» единиц px. Пиксели — это зло.
Поэтому я сделал второй вариант полностью на em (кроме ширины границы). И именно из-за этой однопиксельной рамки могут появляться небольшие зазоры при увеличении размера шрифта. Выход — не используйте границу =)
# 2138 дн. назад:
# 2138 дн. назад:
# 2063 дн. назад:
# 2027 дн. назад:
# 1985 дн. назад:
# 1971 дн. назад:
# 1948 дн. назад:
# 1943 дн. назад:
# 1940 дн. назад:
# 1934 дн. назад:
# 1929 дн. назад:
# 1921 дн. назад:
# 1911 дн. назад:
# 1911 дн. назад:
# 1910 дн. назад:
# 1910 дн. назад:
# 1889 дн. назад:
# 1887 дн. назад:
# 1815 дн. назад:
# 1804 дн. назад:
# 1802 дн. назад:
# 1791 дн. назад:
# 1790 дн. назад:
# 1742 дн. назад:
# 1740 дн. назад:
# 1737 дн. назад:
# 1709 дн. назад:
# 1698 дн. назад:
# 1691 дн. назад: