Резиновое меню из блоков
Задача
Сделать резиновое меню, которое занимает всю доступную ширину родителя. При этом пункты меню должны быть прижаты друг к другу: меню как бы в виде блоков.
Требования
- семантический код на списках (никаких таблиц);
- без использования Javascript и серверных вычислений;
- возможность добавлять/уменьшать количество пунктов меню с сохранением первоначального дизайна;
- адаптация под разные разрешения при резиновых дизайнах.
Решение (вариант 1)
Для решения такой задачи идеально подходит таблица со своей способностью «приспосабливаться к внешним условиям». Каких-либо других элементов, которые обладали бы данной особенностью я не знаю. Единственное, что пришло на ум — это эмуляция поведения таблицы для списка. В этом нам поможет CSS свойство display и его значения table-row и table-cell. И так, в HTML у нас обычный список:
<nav> <ol id="nav"> <!-- id тут нужен только для IE6-7 и только для резиновых дизайнов --> <li><a href="#">Главная</a></li> <li><a href="#">Собираем</a></li> <li><a href="#">Без цензуры чхать на цензуру</a></li> <li><a href="#">Учимся</a></li> <li><a href="#">Справочники</a></li> </ol> </nav>
ol { display: table-row; /* эмуляция строки таблицы */ } li { width: auto; /* чтобы поведение было аналогичным ячейки таблицы */ display: table-cell; /* эмуляция ячейки таблицы */ text-align: center; height: 50px; /* высота пункта */ padding-left: 2px; /* визуальная граница между пунктами меню */ vertical-align: bottom; /* это нужно для случаев, когда в пункте меню текста больше чем на одну строку */ } li:first-child { padding: 0; /* у первого элемента убираем отступ чтобы четко прилег к левой границе */ } a { width: 1000px; /* вот это заставляет наши псевдоячейки растянуться должным образом */ height: 50px; display: table-cell; /* без этого тоже никак */ vertical-align: middle; /* вертикальное выравнивание текста */ }
Этого достаточно для большинства популярных ныне браузеров. Но так как IE6 и 7 все еще присутствуют на рынке, без «шаманства» не обойтись. Во-первых, не забываем подключать html5shiv или modernizr, если используется HTML5 разметка. Во-вторых, пишем еще приличный кусок CSS кода для этих браузеров (только не забудь вынести его в отдельный CSS и подключить только для IE нужных версий):
nav { /* эти правила нужны чтобы устранить ряд небольших багов ие */ display: block; width: 100%; overflow: hidden; position: relative; } li { display: inline; /* table-cell мы же не понимаем */ zoom:1; overflow: hidden; } ol { display: block; position: relative; height: 50px; overflow: hidden; /* ниже идет мега шамантсво, где рассчитывается ширина пункта меню в зависимости от текущей ширины меню и к-ва пунктов в нем */ z-index: expression(runtimeStyle.zIndex = 1, function(node) { var list = node.childNodes, i = list.length, iWidth = node.offsetWidth/i; while(i--) { list[i].style.width = iWidth+&px&; } node.style.width=node.offsetWidth+10+"px"; /* 10 - это значение подбираем по обстоятельствам. нужно чтобы меню выстроилось в одну строку (устраняем погрешности округлений вычислений) */ }(this));) } a { width: auto; height: auto; display: block; z-index: expression(runtimeStyle.zIndex = 1, this == ((50/2)-parseInt(offsetHeight)/2) <0 ? (style.paddingTop="0",style.height="50px") : (style.paddingTop=(50/2)-(parseInt(offsetHeight)/2) +&px&, style.height="50px")); /* вертикальное выравнивание */ }
И это еще не все. IE6 нужно еще научить понимать first-child. А если дизайн у нас резиновый, тогда нам нужно пересчитывать ширину пунктов меню при изменении размеров экрана:
window.onresize = function() { var nav = document.getElementById("nav"); nav.style.width = "100%"; var list = nav.childNodes, i = list.length, iWidth = nav.offsetWidth/i; while(i--) { list[i].style.width = iWidth+&px&; } nav.style.width=nav.offsetWidth+10+"px"; }
Демо пример. Проверено в:
- IE 6-9
- Firefox 4
- Opera 11
- Safari 5
- Chrome
- iPhone 3GS
Если код копируешь из примеров, не забудь удалить комментарии из CSS — старые IE не всегда адекватно на их реагируют.
P.S.
Хотя для старых IE решение громоздко, изобилует экспрешенами, все же это работает достаточно быстро даже в 6-м. И я придерживаюсь мнения, что лучше сайт будет работать медленнее на старых браузерах, но лучше на новых.
display:table VS display:table-row
Update by Александр Головко.
В комментариях у Seva возник резонный вопрос, почему собственно используется достаточно экзотическое display: table-row, если можно прописать display: table и в этом случае избавится от скользкого правила:
a { width: 1000px; }
Почему скользкого? Потому что оно не позволит задать ссылке правый паддинг. А когда речь идет о красиво оформленном меню на макете, это может быть важно.
Для того чтобы этот вопрос решить, я модифицировал первоначальный демо-пример. Результат получился довольно неоднозначный.
Фактически вышло три варианта решения — ol, представленный в табличном виде можно принудительно растянуть на всю ширину (что для table-row получалось автоматически), а можно и не растягивать.
И в том и в другом случае результат отличается от базового (с table-row) тем, что пункты меню для всех нормальных браузеров будут неодинаковыми по ширине (пропорционально своему содержимому). Конечно, в IE7 все будет одинаково, ты ведь помнишь, там это обеспечивает шаманство, с которым не поспоришь.
Третий случай получаем, если все-таки растянуть ссылки — насколько я могу судить, результат тогда не отличается от table-row.
Чтобы окончательно понять разницу, посмотри демо пример со всеми тремя вариантами. Обрати внимание, как ведет себя меню при маленьком и большом разрешении. Я специально сделал затемнение фона ссылки при ховере, чтобы были видны слабые места полученных результатов.
Мораль примерно такова: хочешь пункты меню одинаковыми по ширине — используй метод с table-row, хочешь разные, можно писать display: table. В общем, имеем разные варианты, каждый выбирает себе по душе (или по дизайну).
Вариант 2: display:table
Update by Seva.
Я не совсем это имел ввиду... Для всех случаев только table. Объясню детальнее, почему table, а не table-row.
Есть понятие анонимных объектов таблицы, они в определённых ситуациях добавляются. Когда ставим table-row и даём ей 100% то создаётся анонимный объект table (вокруг table-row) у которого нет никакой ширины и по дефолту не 100% поэтому table-row не от кого отсчитать ширину поэтому получается не 100%, а также, по-моему, table-row нельзя задать ширину она равна ширине таблицы (а для таблицы так как это в данном случае анонимный объект не указана ширина). Поэтому и пихаются эти своеобразные костыли-распорки:
a { width: 1000px; }
кстати с таким же успехом можно и
li { width:1000px; }
написать, а с ссылкой делать то что нужно падинг задать или блоком сделать чтоб на всю ширину, что угодно... Но это всё равно не красиво! Так как во-первых width:1000px; это явный костыль, во вторых, чисто теоретически, возможна ситуация что кто-то искусственно задаст очень большую ширину их родителю и 1000px будет мало )))
В принципе при display:table эти костыли тоже можно ставить, только они не нужны потому что:
Если мы ставим display:table; то между table и table-cell создастся анонимный объект: строка(table-row) может ещё и tbody не важно, а важно то что таблице мы можем задать 100% и ячейки сами подстроятся под нужную ширину, и вот тут есть два варианта:
- мы хотим, чтобы ячейки были шириной относительно в них содержащегося контента.
- мы хотим равные по ширине ячейки...
В первом случае ничего не трогаем оно так и получается по дефолту, для уверенности можно написать для ol (или ul) table-layout: auto хотя оно по дефолту стоит.
Во втором случае есть несколько вариантов сделать распорку width:1000px; для <li> или <a> но это криво. Намного лучше написать просто table-layout: fixed для ol (или ul) и все.
Кстати во всех этих способах есть ещё одна важная деталь!
Мы можем играться со свойством border-collapse: separate; (оно по дефолту стоит вроде) то есть у нас по две рамки между пунктами меню можем менять их, и расстояние между ними почти как и при работе с блоками только вместо марджина (марджин ячейкам нельзя ставить) юзаем border-spacing для элементов <ol> (или <ul>). Если это не нужно, то делаем обычную border-collapse: collapse; и у нас будут общие рамки у элементов <li>.
Демо пример (Примечание редакции: осторожно — пример не снабжен костылями для IE7!).