Пользователь
0,0
рейтинг
13 октября 2010 в 11:42

Разработка → Tableless justification или inline-blocks revisited

CSS*
Все давно знают про кроссбраузерную реализацию инлайн-блоков, но не все знают, что данная реализация не такая уж и кроссбраузерная и полная, как кажется. Что, как и почему рассмотрим на простом примере: сделаем меню, пункты которого равномерно распределены по всей ширине экрана.

Обычно в разметке меню выглядит так:
<ul class="menu">
    <li class="menu-item">
        <a href="/news">Новости</a>
    </li>
    <li class="menu-item">
        <a href="/swen">Старости</a>
    </li>
    <li class="menu-item">
        <a href="/profit">Всякая информация</a>
    </li>
</ul>

А css для этого примерно таков:
.menu-item {
    display: inline-block;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

В итоге получается, что каждый пункт меню — это слово в строке (независимо от того, сколько слов внутри пункта, т.к. инлайн-блок — неделимая единица со своим контекстом форматирования) и чтобы слова растягивались на всю ширину строки нужно сделать следующее:
.menu {
    text-align: justify;
}

.menu-item {
    display: inline-block;
    text-align: left;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

Но желаемого результата это не даст, т.к. в css нет значения full-justify для свойства text-align, а обычный justify растягивает расстояния между слов в строках, кроме последней, так что нам нужно вписать какое-нибудь достаточно длинной слово, которое бы переносилось на новую строку (в css3 есть text-align-last для манипуляции выравниванием последней строки, но это прокатит лишь в светлом будущем). Писать слово, конечно, не очень хороший вариант, достаточно просто сделать ещё один пункт меню и растянуть его на 100% ширины родителя (можно и меньше 100, тогда можно добиться довольно интересного эффекта):

<ul class="menu">
    <li class="menu-item">
        <a href="/news">Новости</a>
    </li>
    <li class="menu-item">
        <a href="/swen">Старости</a>
    </li>
    <li class="menu-item">
        <a href="/profit">Всякая информация</a>
    </li>
    <li class="menu-item menu-item_sizer">
        &nbsp;
    </li>
</ul>

.menu {
    text-align: justify;
}

.menu-item {
    display: inline-block;
    text-align: left;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

.menu-item_sizer {
    width: 100%;
}

Данный код будет одинаково хорошо работать во всех браузерах… кроме Internet Explorer 6 и 7 (кто бы сомневался). А проблема кроется в том, что такие «инлайн-блоки» в данных версиях эксплорера «слишком блочные» — они не учитывают пробельные символы вокруг себя. Наглядный пример:
<span class="inlineBlock">Test</span> word

В нормальных браузерах это будет выглядеть так:
Test word

В старых версиях эксплорера так:
Testword

К счастью есть решение этой проблемы и лежит оно на поверхности — надо сделать этот «инлайн-блок» более инлайновым, для это его нужно обернуть в инлайн элемент. Что получается:
<ul class="menu">
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/news">Новости</a>
        </div>
    </li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/swen">Старости</a>
        </div>
    </li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/profit">Всякая информация</a>
        </div>
    </li>
    <li class="menu-item_wrap menu-item_sizer">
        &nbsp;
    </li>
</ul>

.menu {
    text-align: justify;
}

.menu-item_wrap {
    display: inline;
}

.menu-item {
    display: inline-block;
    text-align: left;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

.menu-item_sizer {
    width: 100%;
}

Но и это не даст нужного результата, хотя всё вроде бы как надо. Проблема заключается в том, что эксплорер не считает каждый пункт единым целым и поэтому всё распадается — дело в пробельных символах закрывающей последовательности тегов. Фиксится просто:
<ul class="menu">
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/news">Новости</a></div></li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/swen">Старости</a></div></li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <a href="/profit">Всякая информация</a></div></li>
    <li class="menu-item_wrap menu-item_sizer">&nbsp;</li>
</ul>

Если вы пользуетесь шаблонизатором, то пусть эту работу сделает он (например тег spaceless в django templates или jinja).
Теперь всё выглядит хорошо и так как нужно во всех браузерах… пока мы не хотим сделать что-нибудь интересное внутри каждого пункта, пусть это будет иконка прижатая к правому краю каждого пункта:
<ul class="menu">
    <li class="menu-item_wrap">
        <div class="menu-item">
            <span class="menu-item-icon"> </span>
            <a href="/news">Новости</a></div></li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <span class="menu-item-icon"> </span>
            <a href="/swen">Старости</a></div></li>
    <li class="menu-item_wrap">
        <div class="menu-item">
            <span class="menu-item-icon"> </span>
            <a href="/profit">Всякая информация</a></div></li>
    <li class="menu-item_wrap menu-item_sizer">&nbsp;</li>
</ul>

.menu {
    text-align: justify;
}

.menu-item_wrap {
    display: inline;
}

.menu-item {
    background-color: Green;
    display: inline-block;
    padding: 5px 15px;
    position: relative;
    text-align: left;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

.menu-item_sizer {
    width: 100%;
}

.menu-item-icon {
    background-color: Red;
    height: 5px;
    position: absolute;
    right: 0;
}

Во всех браузерах включая старые эксплореры всё ок… кроме всех версий Оперы (кто бы сомневался). В Опере можно увидеть, что иконка не прижата к правому краю пункта, а находится левее — это банальный баг, который будет мешать жить разработчикам ещё лет 10, не меньше. Заключается он в том, что если в inline-block элементе находится не блочный элемент или не текст, происходит нарушение границ позиционирования. Фиксится это добавлением ещё одной, внутренней, блочной обёртки.
<ul class="menu">
    <li class="menu-item_outerWrap">
        <div class="menu-item">
            <div class="menu-item_innerWrap">
                <span class="menu-item-icon">&nbsp;</span>
                <a href="/news">Новости</a></div></div></li>
    <li class="menu-item_outerWrap">
        <div class="menu-item">
            <div class="menu-item_innerWrap">
                <span class="menu-item-icon">&nbsp;</span>
                <a href="/swen">Старости</a></div></div></li>
    <li class="menu-item_outerWrap">
        <div class="menu-item">
            <div class="menu-item_innerWrap">
                <span class="menu-item-icon">&nbsp;</span>
                <a href="/profit">Всякая информация</a></div></div></li>
    <li class="menu-item_outerWrap menu-item_sizer">&nbsp;</li>
</ul>

.menu {
    text-align: justify;
}

.menu-item_outerWrap {
    display: inline;
}

.menu-item {
    background-color: Green;
    display: inline-block;
    padding: 5px 15px;
    position: relative;
    text-align: left;
}

* html .menu-item {
    display: inline;
    zoom: 1;
}

*+html .menu-item {
    display: inline;
    zoom: 1;
}

.menu-item_innerWrap {
    display: block;
}

.menu-item_sizer {
    width: 100%;
}

.menu-item-icon {
    background-color: Red;
    height: 5px;
    position: absolute;
    right: 0;
}

Теперь главное не включить какому-нибудь блочному элементу внутри .menu-item hasLayout в IE6, т.к. это спровоцирует «распирание» нашего инлайн-блока на всю ширину. Решить это можно задав фиксированную ширину блочному элементу (если нужна ширина по содержимому нужно задать нулевую ширину, но только для IE6).

С одной стороны получилось непросто и не совсем красиво, а с другой стороны мы получили решение, которое ведёт себя как надо и может использоваться без опаски (почти) и в других местах, например для создания своих собственных элементов управления, которые бы вели себя в контенте подобно стандартным браузерным элементам управления (чтобы изменять поведение такого элемента управления нужно всё применять к инлайн-блочной его части, например если вы захотите сделать элемент блочным или плавающим).

Ну и для закрепляющего эффекта, «формула» настоящего инлайн-блока в zen-css-подобном псевдокоде:
inline>inline-block>block


Может кто-то всё-таки донесёт до администрации, что нам тут нужна подсветка CSS? А то моя просьба как-то была проигнорирована.
Вася Аксёнов @ArtyV
карма
22,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (66)

  • 0
    Спасибо может где то пригодится

    правда многовато лешних тегов.
    если забить на ие6 то можно уменьшить код
    • 0
      Можно уменьшить код тремя путями:
      1. Забить на IE6 и IE7
      2а. Забить на Opera
      2б. Не использовать абсолютное позиционирование внутри инлайн-блока
      • +1
        кстати да
        1 забить на ие6 на 7 еще рановато
        2 Юзать иконки через беграунд проблема отпадает

        Тогда код станет явно юзабильней

        Хотя с семеркой можно тоже попробовать что то решить без лишних тегов.
        • 0
          Иконки — это довольно притянутый за уши пример, можете придумать какой-нибудь свой, более реалистичный : D у меня в реальности была несколько другая задача

          Хотя с семеркой можно тоже попробовать что то решить без лишних тегов.

          IE7 работает на том же самом движке, что и IE6, но немного допиленном, но модель отображения у них одинаковая, так что ничего не поделать.
  • –3
    Ну не нравятся мне дивы в листах. Как валидировать-то будете?
    • +6
      1. Вопрос личных предпочтений мало кому интересен.
      2. По спецификации в элементах типа li возможно использование любых элементов, которые могут быть внутри body
      • +2
        Да. С валидатором — погорячился
  • 0
    насколько я знаю, эксплорер как раз корректно обрабатывает пробел в инлайн элементах, самое простое решение — удалить пробелы после закрывающих тегов инлайн элементов.
    • 0
      Ну tracing whitespace в инлайн элементах понятное дело вещь лишняя и опасная, но инлайновости это «инлайн-блокам» не добавит
      • 0
        trailing, простите
  • +18
    Хотелось бы картинок в статью, чтобы видеть что вы добиваетесь и что получается.
    Прогонять весь ваш код в разных браузерах ломает, а без этого статью не могу считать полной и полезной.
    • 0
      Хм, мне казалось, что и так по коду всё понятно. Окей сделаю картинок
      • +1
        Кодеры тоже люди. По картинкам всё воспринимается проще и быстрее :)
  • +1
    На всякий случай:
    — text-align-last работает в IE;
    — следовательно, блок со 100% шириной можно добавлять через :after (пускай и не во всех браузерах получится обнулить его высоту) — избавление от лишнего блока.
    • 0
      В IE и только в IE, причём начиная с IE8
      • 0
        Начиная, как минимум, с шестого :)
        • 0
          Хм, а по msdn не скажешь
          • 0
            Ну если всегда только msdn слушать… Практическое подтверждение порой тоже полезно.
            • 0
              Сейчас нет возможности проверить, поэтому положился на msdn, зря видимо: )
  • 0
    вот это матрешка у вас получилась)) но с ослами видать по другому нельзя…
    PS зачем такой иностранный заголовок? — чуть глаз не сломал, когда в англ. речи об «или» споткнулся…
    • 0
      По-русски не знаю как это всё написать
  • +1
    как-то изобрел подобное с обертками ~ год назад (кроме оперного решения) и оно успешно работало на зарубежном проекте, но когда время пришло интегрировать в отечественный ресурс меня ткнули носом в оперу и у меня не хватило ума для борьбы с ней, засунул все в таблицу.

    Шикарно, достаточно типовая задача, которая приносит много проблем, а решения в интернете пестрят js-кодом.
  • +5
    Хотелось бы посмотреть демо.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Где вы были 3 дня назад? Пришлось заменить LI — меню на TABLE, чтобы оно стало резиновым )))
    • 0
      Разумный выход.
      chikuyonok.ru/2010/01/liquid-site-markup/ — также микстура
      • +1
        А это тут причём? Не понятно
      • +1
        налицо полное непонимание предметной области %\
        • –1
          Чем это не решает задачу равномерного распределения по ширине?
          • +1
            Тем, что «распределение» происходит не автоматически, а ручками + фиксированная ширина каждого элемента
  • 0
    По поводу предпоследнего варианта, где автор указывает на ошибку в Opera: вы не могли бы пояснить в чем конкретно ошибка, ссылаясь на некий документ с описанием, который нарушила Opera?
    Потому как я вижу абсолютно-позиционированный блок (menu-item-icon), который должен позиционироваться относительно своего родителя (menu-item), потому как это первый предок с position=relative|absolute. У этого предка установлен padding. Почему menu-item-icon должен его игнорировать?
    Я не исключаю пробела в своих знаниях, потому и спрашиваю :) Сам на w3.org пояснения не нашел.
    По адресу www.w3.org/TR/CSS2/box.html#box-padding-area наглядно указана зона «Content» в которую и входит наш menu-item-icon…

    спасибо.
    • 0
      Это в данном случае повезло, что он прилип к паддинг-боксу, что как бы показывает, что это может и нормально. Но в то же время, если вы прижмёте элемент к левому краю, то он и прижмётся к левому краю, а не к левому краю паддинг-бокса. А вот пример поинтереснее: dl.dropbox.com/u/907871/opera%20inline-blocks.rar
      А как надо позиционироваться при абсолютном позиционировании написано тут: www.w3.org/TR/CSS2/visuren.html#choose-position
      • 0
        bug report отправляли?
        • 0
          Конечно же!
  • 0
    А можно ссылку на рабочий пример?
  • +2
    А хотя не надо. Уже сам протестил.
    Ну что могу сказать: способ, честно говоря не очень хороший. Очень много оберток элементов + лишний элемент для эмуляции последней строки. Этого всего можно избежать. Во-первых, ie, начиная с 6-ой версии поддерживает свойство text-align-last, а для других браузеров можно использовать свойство content: '' и задавать ему width: 100%. А во-вторых (и это я сам недавно узнал), чтобы ie мог применить свойствой text-align: justify к псевдо-инлайнблочным элементам нужно для него прописать свойство text-justify: newspaper. И тогда всё загадочным образом работает.
    • 0
      Пример с джастификацией тут как бы за одно показан, это здорово что нашлись более хорошие варианты как это сделать, главное (для меня, по крайней мере) было показать как сделать «нормальный» инлайн-блок.
  • 0
    В последнем примере должно быть
    .menu-item_outerWrap {
        display: inline-block;
    }
    

    иначе вся соль пропадает (Но IE6,7 по прежнему не согласны).
    • 0
      Да, возможно опечатка
  • 0
    Кому интересно, вот мое решение на JS при простом хтмле (ul, li, a)

    function nice_menu () {
    var total_length = main_menu.width();
    var li_width = 0;
    for (var i = 0; i < li.length; i++ )
    {
    li_width = parseInt(li[i].clientWidth) + li_width;
    }
    var li_margin = (total_length — li_width)/(li.length — 1);
    li_not_last.css('margin-right', li_margin+'px');
    li_last.css('margin','0');
    }

    И вешаем эту функцию на domReady и window.onresize

    Думаю, не стоит пояснять, что нужно вносить в переменные типа li_last.

    Если парсер съест код, то поясню алгоритм:
    Считаем ширину всего ul-меню, затем считаем ширину всех li, ищем разницу между ними. Затем эту разницу делим на (кол-во элементов в меню минус один) и присваем это значение в виде правого марджина всем элементам меню, КРОМЕ последнего. ему присваиваем 0.
    Достаточно быстро работает даже в 6 ИЕ, багов не выявлено.
    • 0
      Решение тривиальное и очевидное, не интересно
      • 0
        Тривиальное и очевидное (а что плохого?), зато без 100500 неочевидных тегов и стилей. Программист либо «натягивальщик на бесплатный движок» будут очень благодарны.

        Ваше решение очень пригодится, когда клиент корпоративный с отключенным из-за безопасности JS.

        И да, кому не интересно, можно не писать об этом, читаем 1ую строчку моего коммента.
        • 0
          Я предлагаю не какое-то готовое решение, а аналитический материал. Никаких неочевидных тегов и стилей там нет, зачем всё это нужно описано.
  • 0
    Во первых:
    menu-item_sizer должен быть inline-block
    т.к. ширина не может быть указана для inline элемента

    Во вторых:
    .menu-item_innerWrap {display: block;}

    В ие6-7 будет распирать элемент на всю ширину
    так что нужно сделать его либо инлайновым, либо float'ным
    (конечно же, только для ие6-7)

    В третьих:
    Нихрена не понял об
    «пробельных символах закрывающей последовательности тегов».
    Копипаст же приведённого вами кода — не работает.
    Однако, решил проблему путём добавления,
    после каждого li, символа неразрывного пробела ( )
    (это не валидно, поэтому — пользуемся условными комментариями)
    Буду очень признателен, если растолкуете этот момент.

    Демка (с малость оптимизированным мною кодом):
    ne-vel.ru/demo/inline_blocks_revisited/

    ЗЫ:
    Спасибо за статью,
    думаю, она многим будет полезна.
    Но уделять внимание мелочам, в будущем — будет очень даже не лишним…
    • 0
      С примерами небольшая проблема, да. Я их позже поправлю. Решение с добавлением пробелов не очень верное, т.к. цель была заставить всё работать во всех браузерах одинаково при одинаковой разметке и примерно одинаковых стилях (т.е. полностью и надёжно эмулировать инлайн-блок). Про распирание я написал, по идее такая проблема возникает только когда есть hasLayout у блочного элемента, решение возможное я тоже привёл ближе к концу статьи.
  • 0
    К счастью есть решение этой проблемы и лежит оно на поверхности — надо сделать этот «инлайн-блок» более инлайновым, для это его нужно обернуть в инлайн элемент
    Предпочитаю убирать пробелы совсем (кроссбраузерно) и задавать отступ между «инлайн-блоками» самостоятельно через margin:
    для этого родительскому блоку (в данном случае .menu) надо задать font-size:0 и не забыть вернуть размер шрифта для .menu-item a
    • 0
      Это не решает ни одной из поставленных задач.
      • 0
        Если для вас это не рашает ни одной поставленой задачи, значит вы не внимателно прочитали комент
        • 0
          Совершенно внимательно. Была задача сделать так, чтобы элементы были распределены равномерно по всей ширине страницы, автоматически конечно же. А также была задача заставить инлайн-блок работать одинаково везде.
  • 0
    Да, кстати, у меня не работает последняя версия вашего кода. Ссылку можно на рабочий пример?
    • 0
      Да, тут не совсем верно, т.к. я писал это по памяти :D потом всё переписал, почитать и примеры можно тут: webanalitik.info/magazine/read/269/ на 40 странице
      • 0
        А демо есть?
        • 0
          Вот тут можно посмотреть меню (ораньжевенькое): e-kontur.ru/possibilities
          Правда после релиза в ие он почему то сломалось :D
          Вот же пример из журнала: jsfiddle.net/RyMNj/
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      В таком случае проблема комплексная, если из-за CSS возникает проблема в DOM ;)
      Про лекарство без оборачиваний уже писали выше (с newspaper), но это не главная цель. Главная цель — одинаковое поведение при равных условиях во всех браузерах
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Такое поведение не только у LI, но и других блочных элементов. Хотя да, это в парсере забито, что эти элементы блочные изначально.
          newspaper помогает сделать распределение по всей ширине, в то время как цель всего этого — создать идеальный инлайн-блок :) без привязки к этому решению
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              А данном случае — да. А если хочется совсем универсальное решение :))) в моём случае мне нужно было создавать свои кастомные элементы управления, поэтому и пришлось придумывать как сделать настоящие инлайн-блоки. С выравниванием — всего лишь пример
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  В посте не очень корректный код, писал по памяти. Вот тут я уже поправился и всё написал как надо: webanalitik.info/magazine/read/269/
                  • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Не понимаю почему, но содержимое списка не занимает 100% ширины, никак не могу побороть: jsfiddle.net/Qcr3V/4/
  • 0
    Уперся сейчас в проблему с лишним отступом снизу у обворачивающего блока. Пробежался по примерам из комментов — у всех та же проблема, кроме этого варианта

    Долго мучался, ища разницу. В итоге оказалось, что с xhtml доктайпом отступа нету, если же использовать html5 доктайп, то он появляется.

    В одном из примеров этот отступ убрали, дописав «margin-bottom:-0.7em». Неудачное решение, так как используется по сути подогнанная цифра.

    Сам же ничего, кроме как установить «line-height: 0» для обертки, не нашел. Такое решение совсем не нравится, но ничего лучше не придумалось.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.