Перейти к навигации · Перейти к содержимому

Выпадающее меню на CSS

Про выпадающие меню на 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 (кроме ширины границы). И именно из-за этой однопиксельной рамки могут появляться небольшие зазоры при увеличении размера шрифта. Выход — не используйте границу =)

Ссылки по теме

Комментарии

Хороший материал. Симпатичное меню получается.

Вот только этот осёл (IE) всюду лезет. В конкретном случае заставляет использовать JavaScript.

Возможно будет полезно:
PNG, альфа прозрачность и Internet Explorer
Свойство :hover для любого CSS элемента

Никита: спасибо. а вот тут как раз обсуждается что лучше: .htc или JS (внизу страницы).
Я лично тоже предпочитаю JS

Спасибо вам большое за скрипт очень помогли :) !!!

Спасибо. Информация на этой странице мне очень пригодилась.

Полезный материал. Вот тока под Mac IE работать не хочет ни в какую…

Спасибо за блог, день потратил на поиски не глючного дроп-меню.

не понимаю, что-то все делаю пошагово, но получается совсем не то, что у вас :(

не понимаю, что-то все делаю пошагово, но получается совсем не то, что у вас :(

Если есть конкретные вопросы, то можно писать на почту. Если будет время — могу помочь

Отличное пособие, все пригодилось. Считаю, что прозрачностью можно пренебречь. Проверенно на индексацию: отлично.

Спасибо за информацию! Очень пригодилась! Только у меня есть один вопросик… Можно ли сделать css-меню (работающее в IE5/6) выпадающее не на наведение мышью, а на клик, и при этом оно и сворачиваться должно так же по клику??? То есть написать в css можно все, а где бы найти скрипт для java… :(

Respect! Однозначно!
Самое главное – «Вы разделяете содержимое и представление». С точки зрения программизма – пять баллов по пятибальной.

Все хорошо, но как отцентрировать меню?

Задали ширину каждой ссылки 120 пикселей (справедливости ради, надо сказать, что реальная ширина равна 140 (120 + 10 + 10) пикселям, так как в нее включаются и отступы)

Если не ошибаюсь, то padding не изменит реалную ширину блока. Он задаст отступ котнтента внутри блока. А ширина в себя это тоже будет включать. Вот если было бы
margin: 5px 10px;
тогда да. Хотя могу ошибаться…

Если не ошибаюсь, то padding не изменит реалную ширину блока.

Ошибаетесь. Реальная ширина в себя включает и padding и margin и даже border. Не включает только IE 6 в режиме обратной совместимости, но он это делает вопреки спецификации.

Супер! Как я мучился с выпадающим меню.. А тут бац! И все супер…

Только один вопрос..
Имеем:
Ширина всего меню 100%, в меню 5 пунктов.
Я хочу, чтобы каждый из них занимал по 20%.
Что бы хорошо выглядело на разных экранах..
Но никак не получается это сделать!!
Помогите пожалуйста!

Ширина всего меню 100%, в меню 5 пунктов.
Я хочу, чтобы каждый из них занимал по 20%.
Что бы хорошо выглядело на разных экранах..

Это тяжело, так как в эксплорере иногда 20×5 != 100%, а равно 100% + 1 пиксель. Поэтому будет слетать вниз при изменении размера окна. Можно конечно повозиться с разными вариантами — все величины надо указывать в процентах. Но не уверен, что будет идеально работать.

в мозилле не пашет

Выношу респект автору (:
Т.к. сам являюсь линухоидом, то посмотрел менюшку тока в Firefox 2 и в Konqueror (KHTML). Работает «на ура» даже в «Завоевателе», что удивляет (: Следовательно, работаеть будет даже в «Сафари», что ещё более удивительно ((:

Предлагаю немного переделанный вариант и упрощенный до минимума + не нужны абсолютно никакие скрипты.
Для правильной работы меню нужен либо MSIE не ниже 7-й версии, либо любая Opera, либо любой Mozilla Firefox.
На остальных браузерах не проверялось :(

#nav, #nav ul {
margin: 0;
padding: 0;
list-style: none;
float: left;
width: 100%;}

#nav li {
display: block;
float: left;
position: relative;
width: 150pt;}

#nav a {
display: block;
background-color: cornflowerblue;
padding: 10pt;
color: white;
text-decoration: none;}

#nav a:hover {
background-color: red;
text-decoration: underline;}

#nav li ul {
display: none;
position: absolute;
width: 150pt;}

#nav li:hover ul {
display: block;
width: 150pt;}

#nav li:hover li ul {
display: none;
left: 150pt;
top: 0pt;}

#nav li:hover li:hover ul {
display: block;
width: 150pt;}
Большое спасибо автору за статью – очень помогла.

Блин! Классно, все четко и подробно. Спасибо автору.

Статья полезная, но как сделать чтобы меню было по центру страницы?

Класс. Это именно то что нужно. Спасибо. Единственно не совсем пойму, у меня все работает и в IE 7 без применения яваскрипта. Очень хочется так и оставить.

Руководство по созданию выпадающего меню отличное.
Единственно хочу предупредить, код стилей для меню желательно прописывать на каждой странице сайта, а не в отдельном файле (style.css). Иначе, при загрузки страниц с подобным меню, если код стилей для меню прописан в отдельном файле (style.css), в течении первых 2–3 секунд возникает эффект размытого меню (в виде длинного списка), так же не красиво смотрятся если их решат сохранить на компьютере с помощью Oper-ы. Подобного не происходит, если код стилей прописан на каждой странице.

А можно ли с помощью CSS сделать эффект плавного (не такого быстрого по скорости) раскрытия меню? (Как где-то читала «для посетителей с замедленной моторикой для лучшего их восприятия» :-).

А вот мне помоч может кто, как мне ее вертикально сделать? =) И Вот вообще супер бы было если задержку сделать хотяб сикунду…

Интересно, а можно сделать меню для пунктов нефиксированной ширины?

Спасибо, очень помогло.

#nav, #nav ul {
margin: 0;
padding: 0;
list-style: none;
float: left;
width: 19%;
 }