Снова равномерное выравнивание блоков по ширине: постепенное улучшение до Flexbox

Задача равномерного выравнивания горизонтальных элементов (например, пунктов меню) по всей ширине контейнера стабильно остается актуальной в верстке. Два года назад Максим Усачев (psywalker) написал обстоятельнейший разбор ее решений, который заслуженно стал самой популярной статьей на CSS-live.ru. Были рассмотрены 4 варианта:

  1. Вариант с разносторонним выравниванием (на базе float), к сожалению, не способный претендовать на универсальность;
  2. Вариант с дополнительным контейнером (в принципе, работоспособное решение, но только для фиксированной ширины элементов);
  3. Вариант с text-align: justify для инлайн-блоков и дополнительным элементом-распоркой (приемлемое решение);
  4. То же самое, но с заменой элемента-распорки на псевдоэлемент :after (лучшее решение).

У двух последних решений была изюминка в виде двух малоизвестных свойств CSS3 (text-align-last и text-justify), по иронии судьбы с незапамятных времен работающих в IE (где они и появились).

Но прогресс открывает нам всё новые возможности, и у старых задач появляются новые, более простые решения. Нашлось оно и для равномерного выравнивания.

Разметка во всех примерах будет одна и та же — типичное горизонтальное меню на ul-списке.

1. Достижимый идеал

Теперь у нас есть механизм специально для раскладки макетов — Flexbox. С его помощью задача решается буквально двумя строчками кода:

.menu {
    display: flex;
    justify-content: space-between;
}

Всё! В этих строчках — полное решение нашей задачи. Первая строчка превращает обычный контейнер в «волшебный» флекс-контейнер, а вторая включает равномерное распределение свободного пространства между пунктами, независимо от их ширины. Остальные свойства — фон, шрифт, отступы и т.п. — нужны буквально лишь для красоты.

Это решение работает сходу в Chrome 21+, Firefox 22+, Опере 12.1 (Presto) и 17+ (Blink), IE11+, а также актуальных мобильных версиях этих браузеров. Это более 60% по данным caniuse.com на момент написания статьи. Так что в новых проектах для «продвинутой» аудитории, а также в не особо важных местах, где равномерное растягивание важно для красоты, но не критично для функциональности, можете ограничиваться этими двумя строчками. В >60% случаев (дальше — больше) будет красиво, а в худшем случае меню станет из горизонтального вертикальным, но будет работать. Чем не изящная деградация?

2. Возвращение к реальности

Конечно, в реальном мире заказчик с дизайнером требуют, чтобы горизонтальное меню оставалось горизонтальным независимо от поддержки каким-то браузером какого-то свойства. Даже в IE8-10, из которых какое-то представление о флексбоксах есть только у 10-го. Но ведь у нас есть прекрасное готовое решение для IE со времен прошлой статьи! Почему бы не объединить его с «идеальным» решением?

.menu {
    display: flex;
    justify-content: space-between;
    text-align: justify;
    text-align-last: justify;
}
.menu__item {
    display: inline-block;
}

посмотреть результат

Теперь у нас в современных браузерах работают флексбоксы, а в IE8-10 — text-align: justify для инлайн-блоков. А text-align-last избавляет нас от нужды в распорках (даже псевдо-). Кстати, работает оно не только в почти всех IE, но и в Firefox — аж с 12-й версии (правда, требует префикса -moz-).

Больше того, мы можем «в одно касание» добавить поддержку IE7 (и даже, в теории, IE5.5):

.menu {
    display: flex;
    justify-content: space-between;
    text-align: justify; /* IE10-, Firefox 12-21 */
    text-align-last: justify; /* IE10-, Firefox 12-21 */
    :)text-justify: newspaper; /* IE7- */
    :)zoom: 1; /* IE7- */
}
.menu__item {
    display: inline-block;
    :)display: inline; /* IE7- */
    :)zoom: 1; /* IE7- */
}

…вот только надо ли?:) В дальнейших примерах в статье мы обойдемся без хаков для IE7, лишь в финальном примере упомянем эту возможность.

3. Мобилизация резервов

Но старые IE — давно не главная проблема. Сегодня куда важнее мобильные браузеры — прежде всего iOS Safari и встроенный браузер Андроида. И как назло последний не поддерживает ни беспрефиксных флексбоксов, ни text-align-last.

Что же, возвращаться к распоркам? Ну уж нет! Все сколько-либо актуальные WebKit-браузеры поддерживают старую экспериментальную реализацию флексбоксов (спецификации 2009 года). И чем городить хаки, лучше уж подключить ее.

Пара свойств, включающих нужное нам отображение, в старых флексбоксах уже была и называлась так: display: box; box-pack: justify. На первый взгляд, достаточно добавить их контейнеру, с единственным нужным нам префиксом:

.menu {
    display: -webkit-box; /* Android 4.3-, Safari */
    -webkit-box-pack: justify; /* Android 4.3-, Safari */
    display: flex;
    justify-content: space-between;
    text-align: justify;
    text-align-last: justify;
}
.menu__item {
    display: inline-block;
}

Но почему-то это не заработало. Оказывается, в отличие от новых флексбоксов, которые применяют свою «магию» к дочерним элементам флекс-контейнера независимо от их display, старые флексбоксы действовали только на блочное содержимое. И, поскольку нашим пунктам задан display:inline-block, с точки зрения старых флексбоксов они не стали самостоятельными флекс-элементами (flex-items), а оказались в единственном флекс-элементе, в который превратился окружавший их анонимный блочный бокс.

Итак, старые флексбоксы требуют, чтобы display:inline-block у элементов не было, а IE10- и Fx12-21 — чтобы был. Нам нужно «разрулить» этот конфликт. Я выбрал простейший путь — переопределил display для пунктов на тот же -webkit-box, т.е. сделал их блочными флекс-контейнерами «старого образца» исключительно в вебкитьей реализации:

.menu {
    display: -webkit-box; /* Android 4.3-, Safari */
    -webkit-box-pack: justify; /* Android 4.3-, Safari */
    display: flex;
    justify-content: space-between;
    text-align: justify;
    text-align-last: justify;
}
.menu__item {
    display: inline-block;
    display: -webkit-box; /* !хак! для Android 4.3-, должен идти после inline-block */
}

посмотреть результат

В нашем случае внутри пунктов нет сложной разметки, поэтому ничего не поломается. Но помните — это хак, пользуйтесь на свой страх и риск! Если есть сомнения, лучше воспользоваться хаками понадежнее — например, таким, либо привычным приемом комбинации нужного селектора с искусственным селектором, который понимает только Вебкит:

.menu__item {
    display: inline-block;
}
.fake-webkit-hack-selector::-webkit-input-placeholder, .menu__item {
    display: block;
}

Итак, мы заставили наше решение работать во всех десктопных браузерах и в подавляющей массе мобильных, всего один раз ступив на скользкую тропку хаков. Причем наш хак не использует неочевидных побочных эффектов, не полагается на магические числа, единственное, для чего он нужен — включить механизм, используемый нами по прямому назначению, там, где он работает. Даже в худшем случае пользователь увидит функциональное горизонтальное меню. А в наиболее вероятном — еще и красиво выровненное. Чем не постепенное улучшение?

Добавлено 11.04.15. Opera Mini больше не проблема! Обновление на ее серверах по волшебству добавило поддержку флексбоксов даже самым захудалым мобильникам, и теперь наше решение охватывает 93% всех браузеров (caniuse.com) даже без добавки старых IE!

4. Разгон и взлет

Для Safari (десктопных и мобильных) у нас пока работает старая версия флексбоксов, на быстродействие которых есть жалобы. Зато новые флексбоксы работают намного быстрее, и Safari их поддерживает — через префикс.

Всё, что нужно для перевода Safari на новые флексбоксы — еще раз продублировать пару свойств из «идеального» решения, но с префиксом -webkit:

.menu {
    display: -webkit-box; /* Android 4.3-, Safari без оптимизации */
    -webkit-box-pack: justify; /* Android 4.3-, Safari без оптимизации */
    display: -webkit-flex; /* оптимизация для Safari */
    webkit-justify-content: space-between; /* оптимизация для Safari */
    display: flex;
    justify-content: space-between;
    text-align: justify; /* IE10-, Firefox 12-22 */
    text-align-last: justify; /* IE10-, Firefox 12-22 */
}
.menu__item {
    display: inline-block;
    display: -webkit-box; /* хак для Android 4.3-, должен идти после inline-block */
}

Что ж, по сравнению с идеалом для 60% наше решение потеряло значительную часть изящества, но осталось в пределах одного экрана кода:) и только нужных селекторов, и по-прежнему не нуждается ни в каких распорках. И при этом работает во всех десктопных и всех мало-мальски серьезных мобильных браузерах! Теперь мы можем посмотреть в действии окончательный результат:

то же самое — на отдельной странице

Уменьшая поддержку тех или иных браузеров, вы всегда сможете сделать код еще компактнее, убрав ненужные строки. А раскомментировав другие ненужные строки, можно осчастливить им даже последних динозавров. И совсем скоро для всех браузеров будет работать только чистое решение по последнему слову CSS-стандартов.

Кто сказал, что флексбоксы и постепенное улучшение несовместимы?

P.S. Это тоже может быть интересно:

33 Комментарии

  1. Милый Антошечка

    Это что за клоунада? Раскатать портянку на километр, но так и не добиться адаптивного поведения блоков — ламерюги.

    1. Илья Стрельцын (Автор записи)

      Простите, где тут шла речь об «адаптивном поведении»? Задача состояла в равномерности промежутков между пунктами, эта задача решена. Новые условия — новая задача. Что конкретно вас не устраивает?

      1. Милый Антошечка

        Вам же сказано — ламерюги. Ламерюги могут кого-нибудь «устраивать», кроме себя? Впрочем, автор может пофантазировать на тему «где я потерял конечную цель своей писанины и была ли она вообще». А про адаптивное поведение больше не рассуждайте — по сайтику видно, что вам чуждо это понятие.

        1. Злой томат

          Конечная цель «писанины» автора-то как раз вполне понятна, как и той «писанины», которая содержится в предыдущей статье (кстати, большое спасибо обоим авторам за такую интересную и качественную «писанину» :), читается буквально на одном дыхании!).

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

          1. Илья Стрельцын (Автор записи)

            Спасибо! Авторы стараются, чтобы «писанина» была полезной и нескучной :)

            1. SelenIT (Автор записи)

              P.S. А теперь и адаптивность добавили, по просьбам трудящихся и Гугла:)

    2. Максим Усачев

      В статье приводится современное решение равномерного выравнивания блоков по ширине. По ходу статьи описываются все нюансы и подводные камни этого решения. Постепенно автор статьи приходит к универсальному коду, благодаря которому задача равномерного выравнивания решается во всех современных и старых браузерах. Честно говоря, совсем непонятны ваши возмущения.

  2. Мастер Азазаза

    Статья бомба! Я как-то читал про флексбоксы в книге одной тетки про ccs3. Надо вернуться перечитать. Там есть и другие свойства. Если добиться такой поддержки этих свойст с возомжностью верстать только на флексбоксах у меня будет конкретный стояк!

  3. Владимир

    В решении есть баг в ФФ

    когда один из вложенных элементов захочет абсолютно спозиционироваться, решение с флексом застолбит под него место в виде 1 пробельного символа и тем самым верстка будет неопрятной. В варианте 4 такой проблемы нет. Могу продемонстрировать чуть позже
     

    1. Максим Усачев

      А для чего делать флекс-элементы абсолютно спозиционированными?

      1. Владимир

        Например для того что бы один элемент, положение которого зависит от нашего списка, но он имеет какое то иное значение(например, главный элемент списка, или картинка пояснение), был в самом списке и перемещался единым блоком. В решении которое было до этого, это реализовалось правильно, в этом решении верстка едет из за пробела

        1. Максим Усачев

          Я вас понял, но не считаю это багом. Флексбоксы предусмотрены для своих нужд. С помощью них можно решить кучу полезных задач, решение которых до появление этой спецификации было найти либо очень сложно (при помощи жутких костылей), либо вообще невозможно. Видимо для вашей (имхо, нечастой) задачи нужны другие механизмы. Если вам подходит вариант 4, то используйте его, вот и всё:)
           

          update: Хотя, нет, глянули в спеку и действительно оказывается, что это баг фокса:

          An absolutely-positioned child of a flex container does not participate in flex layout.

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

          Поэтому приношу свои извинения и надеюсь, что в ближайших релизах фокса баги будут залечены! Кстати, в IE11 таже проблема:(

          Вам же могу посоветовать решать вашу задачу другим способом. Например, вариант 4, как вы и говорили. Хорошо, что задача нечастая.

          1. SelenIT (Автор записи)

            Там проблема была в том, что сама спецификация невпопад изменилась, когда многие уже реализовали старый вариант (хотя новый, безусловно, логичнее). Но авторы спецификации вроде бы договорились больше так не делать.

  4. Владимир

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

    http://joxi.ru/p27L1J5fxQyDA7

    В варианте с флекс боксами не получится сделать так изменив лишь одно свойство на мидл. 

    Четвертый вариант все таки гораздо универсальнее и работает в большинстве браузеров.

    1. Владимир

      Сам себе отвечаю :)

      -ms-flex-align: center;
      -webkit-align-items: center;
      -webkit-box-align: center;
      align-items: center;

      Не смог найти в caniuse его

      1. SelenIT (Автор записи)

        Всё-таки caniuse — информация о поддержке технологий (напр. флексбоксов) браузерами, а не справочник по пользованию этими технологиями. А возможность легко центрировать практически что угодно в чем угодно по любой оси — как раз преимущество флексбоксов, которое подчеркивается во всех статьях о них (в т.ч. в шпаргалке по ним, опубликованной здесь). Так что как раз это едва ли можно назвать недостатком метода)

        1. Владимир

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

  5. Дмитрий

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

    1. SelenIT (Автор записи)

      Конкретное решение, описанное в статье, охватывает все браузеры, кроме Оперы Мини, и, по-моему, вполне production-ready. В других случаях нужно смотреть по ситуации, но в типовых задачах флексбоксы с «запасным вариантом» в виде флоатов и инлайн-блоков, по-моему, вполне применимы. В 70% случаев (все новые браузеры, включая мобильные), будет красиво, в остальных — чуть менее красиво, но всё равно функционально.

      1. Дмитрий

        Спасибо, т.е. все же можно использовать в реальных проектах эту магию флекса)) я тут погуглил немного, на эту тему, было много материала http://jakearchibald.com/2014/dont-use-flexbox-for-page-layout/ что мол, рано еще его использовать, что float грузится быстрее, но это было год назад, судя по датам статей, на сегодня как я понял, все обстоит гораздо лучше.

        1. SelenIT (Автор записи)

          Насколько я понял, про скорость, тем более в сравнении с флоатами, в той статье речь не идет. Говорится о том, что при медленном соединении загрузка страницы, основная структура которой построена на флексбоксах, может выглядеть некрасиво из-за "перескоков" элементов по мере их подгрузки, но такое может быть и с флоатами. К тому же это можно свести к минимуму заранее продуманным заданием размеров и отступов. Grid layout, да, в теории позволяет загружать блоки страницы без "перескоков", но вот она еще далека от продакшна (поддерживается только старая версия спецификации в IE10+ и экспериментальная реализация в Хроме за флагом).

          Ну а отдельные элементы страницы — те же меню, галереи товаров и т.п. — по-моему, можно "флексить" абсолютно безболезненно.

          1. Дмитрий

            Упс, дал не ту ссылку, вот http://jakearchibald.com/2014/dont-use-flexbox-for-page-layout/ здесь конечно разница в миллисекундах, что в принципе не особо критично, я о том, что хотелось бы полностью перейти на Flex уже сегодня, т.е. делать сайты полностью сверстанные с флексбоксом, это уже возможно как я понял, придется с префиксами повозится (либо autoprefixer юзать), хотелось просто мнение экспертов узнать. Также посмотрел видео этого маэстро, что тоже вдохновило https://www.youtube.com/watch?v=2ujfOyJE7zk

      2. SelenIT (Автор записи)

        UPD: В Опере Мини теперь тоже поддерживается (причем в любой, поскольку отрисовка делается на сервере).

  6. Иван

    Спасибо большое! Решение классное!

  7. yura

    как теперь в этих блоках выровнять по центру текс?? text-align, !important и прочие штуки не спасают

    1. SelenIT (Автор записи)

      В каких именно блоках? В последнем примере текст инлайн-блочных пунктов как раз выровнен по центру, именно через text-align. А можно пример кода, где не получается?

  8. yura

    в хроме, (опере) заметил такой прикол что — если разместить текст в несколько строк то центрируется текст по центру только между первой и последней строй, в ФФ всё ок

    1. SelenIT (Автор записи)

      А можно всё-таки пример на jsFiddle/CodePen/jsBin и т.п. с таким поведением? Очень странно, возможно, баг движка Blink, о котором надо срочно сообщить куда следует.

  9. Spaceoddity

    Практическая применимость данного способа под очень большим вопросом. Да, довольно часто надо сделать «выключку по ширине», но вот постоянное наличие «vertical-align:top;» совсем не обязательно!

    1. SelenIT (Автор записи)

      А кто говорит, что обязательно? :) Оно добавлено чисто для красоты, потому что с умолчательным baseline результат может сильно отличаться от желаемого. Но всё зависит от конкретной задачи.

      1. Spaceoddity

        Хорошо, спрошу по другому — как таким методом сделать выключку по ширине и вертикальное выравнивание по низу (допустим, один из элементов имеет высоту большую чем другие, и, допустим, высота заранее не известна)?

        1. SelenIT (Автор записи)

          По идее, аналогично — только заменить top на bottom и добавить align-items: flex-end: http://dabblet.com/gist/2ee9557cbcabfd6e4bc3

  10. Алексей

    Красиво! А расположить резиновые квадратики в контейнере с равными промежутками между ними и от границы контейнера? ;-)

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.