CSS

Резиновое меню из блоков

Задача

Сделать резиновое меню, которое занимает всю доступную ширину родителя. При этом пункты меню должны быть прижаты друг к другу: меню как бы в виде блоков.



Требования

семантический код на списках (никаких таблиц);
без использования Javascript и серверных вычислений;
возможность добавлять/уменьшать количество пунктов меню с сохранением первоначального дизайна;
адаптация под разные разрешения при резиновых дизайнах.

Решение (вариант 1)

Для решения такой задачи идеально подходит таблица со своей способностью «приспосабливаться к внешним условиям». Каких-либо других элементов, которые обладали бы данной особенностью я не знаю. Единственное, что пришло на ум — это эмуляция поведения таблицы для списка. В этом нам поможет CSS свойство display и его значения table-row и table-cell. И так, в HTML у нас обычный список:
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>


CSS
	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 нужных версий):
CSS
	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. А если дизайн у нас резиновый, тогда нам нужно пересчитывать ширину пунктов меню при изменении размеров экрана:
Javascript
	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

В комментариях у Seva возник резонный вопрос, почему собственно используется достаточно экзотическое display: table-row, если можно прописать display: table и в этом случае избавится от скользкого правила:
CSS
	a {
	width: 1000px;
	}



Почему скользкого? Потому что оно не позволит задать ссылке правый паддинг. А когда речь идет о красиво оформленном меню на макете, это может быть важно.

Для того чтобы этот вопрос решить, я модифицировал первоначальный демо-пример. Результат получился довольно неоднозначный.

Фактически вышло три варианта решения — ol, представленный в табличном виде можно принудительно растянуть на всю ширину (что для table-row получалось автоматически), а можно и не растягивать.

И в том и в другом случае результат отличается от базового (с table-row) тем, что пункты меню для всех нормальных браузеров будут неодинаковыми по ширине (пропорционально своему содержимому). Конечно, в IE7 все будет одинаково, ты ведь помнишь, там это обеспечивает шаманство, с которым не поспоришь.

Третий случай получаем, если все-таки растянуть ссылки — насколько я могу судить, результат тогда не отличается от table-row.

Чтобы окончательно понять разницу, посмотри демо пример со всеми тремя вариантами. Обрати внимание, как ведет себя меню при маленьком и большом разрешении. Я специально сделал затемнение фона ссылки при ховере, чтобы были видны слабые места полученных результатов.

Мораль примерно такова: хочешь пункты меню одинаковыми по ширине — используй метод с table-row, хочешь разные, можно писать display: table. В общем, имеем разные варианты, каждый выбирает себе по душе (или по дизайну).

Вариант 2: display:table

Я не совсем это имел ввиду... Для всех случаев только table. Объясню детальнее, почему table, а не table-row.

Есть понятие анонимных объектов таблицы, они в определённых ситуациях добавляются. Когда ставим table-row и даём ей 100% то создаётся анонимный объект table (вокруг table-row) у которого нет никакой ширины и по дефолту не 100% поэтому table-row не от кого отсчитать ширину поэтому получается не 100%, а также, по-моему, table-row нельзя задать ширину она равна ширине таблицы (а для таблицы так как это в данном случае анонимный объект не указана ширина). Поэтому и пихаются эти своеобразные костыли-распорки:
CSS
	a {
	  width: 1000px;
	}



кстати с таким же успехом можно и
CSS
	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>.
Теги:
Резиновое меню
Добавлено: 16 Августа 2013 12:11:56 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...