Tableless justification или inline-blocks revisited

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

    Обычно в разметке меню выглядит так:
    <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? А то моя просьба как-то была проигнорирована.
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 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 в инлайн элементах понятное дело вещь лишняя и опасная, но инлайновости это «инлайн-блокам» не добавит
        • +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
                          А можно ссылку на рабочий пример?
                          • +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
                                        В таком случае проблема комплексная, если из-за 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» для обертки, не нашел. Такое решение совсем не нравится, но ничего лучше не придумалось.

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