CSS-live.ru

Когда флексбоксы не всесильны, или сюрприз тёмной стороны CSS

Сегодня, когда флексбоксы поддерживаются в 93% браузеров, многие видят в них панацею для верстки. И неспроста: это первый модуль CSS специально для раскладки блоков, многие когда-то сложные задачи с ним решаются буквально парой строчек. Есть даже целая галерея таких решений (ее автор Филип Уолтон знаком нам по систематизации браузерных багов с флексбоксами). Кажется, что этой новой чудесной технологии подвластно всё. Так что легко понять недавнее удивление известного веб-дизайнера, разработчика и популяризатора Уэса Боса в твиттере:

Неужели это невозможно с флексбоксами? Как мне заставить флекс-элементы растягиваться, но выравнивать свой контент по вертикали? Вот код:

Казалось бы, элементарная задача. Всего одна строка флекс-элементов, в самих элементах ничего хитрого, обычное строчное содержимое. И по отдельности требования Уэса реализуются проще некуда. Вертикальное центрирование — одной строчкой кода: align-items:center. Растягивание блоков до одинаковой высоты — вообще без кода, это делает значение align-items:stretch по умолчанию. Одна проблема — свойство принимает лишь одно значение.

В длинном обсуждении твита решения так и не нашлось.

Первая мысль, пришедшая на ум и самому Уэсу, и комментаторам, и вашему покорному слуге — флексбоксы можно вкладывать друг в друга. Можно поставить display:flex самим флекс-элементам, превратив их во флекс-контейнеры для их собственного содержимого, со всем флексбоксовым арсеналом. Еще пара строчек CSS — и на первый взгляд задача почти решена:

See the Pen KpmKJN by Ilya Streltsyn (@SelenIT) on CodePen.

Но присмотритесь внимательнее. Почему выделенные жирным слова слиплись с предыдущими, как будто перед ними нет пробела, а цифра «4» в предпоследней ячейке заняла целую строку, хотя на предыдущей строке перед ней куча места?

Анонимная разгадка

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

Помните, что разные display принадлежат разным «мирам» (контекстам форматирования) и на «стыке» миров встречаются «призраки» — анонимные боксы? Здесь как раз такой случай. Во флексбоксовом контексте не бывает ничего строчного. И обычные дочерние элементы превратились во флекс-элементы, а текст между ними — в анонимные флекс-элементы. И те, и другие могут переноситься на новую строку флекс-элементов (когда контейнеру задано flex-wrap:wrap) — но только целиком. Как инлайн-блоки. Только, в отличие от инлайн-блоков — «живущих» в строчном «мире», где межсловные пробелы значимы — пробелы между флекс-элементами не учитываются.

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

Чтобы решить задачу до конца, нам нужно вернуть строчное содержимое в его родной строчный «мир», т.е. контекст. И единственный способ сделать это — добавить обертку-прослойку. Чтобы «магия» флексбоксов действовала на нее лишь снаружи, а внутри всё шло своим чередом, как в обычном блоке. Увы, без добавочной разметки не обойтись: нельзя быть в двух «мирах» одновременно, по крайней мере в CSS.

Забытая альтернатива

Раз уж могучие флексбоксы не могут справиться с этой задачей, неужели CSS вообще бессилен перед ней? И тут перед нами встает соблазн «тёмной стороны CSS».

Об этом CSS-механизме ходит бесчисленное количество мифов, порой нелепых, иногда небезосновательных. Многие верстальщики говорят о нем лишь со снисходительной усмешкой. Прошлой осенью немало шуму наделала статья «Антигерой CSS-раскладки» (есть ее перевод на htmlacademy.ru), пытавшаяся реабилитировать его, но некоторые заявления этой статьи получились… забавными, и сам автор существенно недооценил мощь этой «темной стороны». А совсем недавно появился целый микрофреймворк на базе этого механизма — правда, его тоже не восприняли всерьез…

Да, речь о старых добрых (или не очень?) CSS-таблицах. И с ним задача Уэса решается даже меньшим количеством кода, чем с флексбоксами!

See the Pen vOmYbM by Ilya Streltsyn (@SelenIT) on CodePen.

Заметьте, что строчные элементы остались строчными, а дополнительной разметки не понадобилось.

Новая старая панацея?

Конечно, нет. У CSS-таблиц хватает своих реальных проблем, прежде всего — браузерных багов. С другой стороны, у флексбоксов их тоже хватает. Я думаю, что в любом случае полезно знать как можно больше инструментов, с их плюсами и минусами — и использовать то, что лучше подходит по ситуации, не ограничивая себя искусственно. Пример этой статьи показывает, что иногда — пусть редко, но всё же — забытые (незаслуженно?) решения вполне могут поспорить с модными новинками. Буду рад любым замечаниям и возражениям в комментариях. И да пребудет с вами сила всего CSS!

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

20 комментариев

    1. Основная проблема, с которой пришлось столкнуться — ячейки таблицы не везде поддерживают position: relative. И, как следствие, чтоб прижать что-то в ячейке с контентом к нижней границе, нужно придумывать костыли.

      1. Это когда было? Уже давно это пофикшено во всех браузерах. Последний был Firefox, но в версии 32-й (или 33-й, уже точно не помню) это было исправлено.

        А вообще, было бы интересно увидеть живой пример, где именно это не работает?

    2. Главная проблема табличного отображения в том, что спецификация для него очень запутанная, браузеры трактуют ее достаточно вольно, и спецификация мало что от них требует:). Для table-layout:fixed (как в этом примере) ситуация получше, но сюрпризы всё равно бывают. Несколько лет назад об этом была занятная статья на Хабре с поучительным разбором. С тех пор ситуация в браузерах чуть улучшилась, но некоторые проблемы остались: Хром так и не научилася делать ячейки нулевой ширины и применять фон к table-row, Firefox, хоть и исправил давние противные баги с позиционированием содержимого ячеек и самих ячеек, неправильно представляет CSS-таблицы скринридерам (как будто это настоящие таблицы), а в IE и проблема с позиционированием (см. в конце) еще актуальна. Но как видим, бывают ситуации, когда эти проблемы не столь существенны, и CSS-таблицы решают задачу самым прямым и логичным способом.

      1. UPD: со времени предыдущего коммента Firefox исправил баг с представлением CSS-таблиц скринридерам. И, похоже, спецификация для табличного форматирования начинает понемногу оживать:)

  1. Так видимо не кошерно, но зачем лечить гланды через ухо?

    I’m short
    How do I make these 100% high?
    short
    I’m a really really 4 line really really really long
    I’m of medium length

    1. В смысле, почему не тегами table/tr/td? Именно потому что, как вы сказали про гланды: нам не нужно, чтобы скринридеры для слепых зачитывали это меню как «Таблица из одного ряда, первая ячейка…», не нужно загромождать память дополнительными DOM-методами типа insertRow и т.п. Всё, что нам нужно — это придать этой штуке таблицеподобное отображение (и то, наверное, не везде и не всегда, а лишь на широком экране, для соответствующего @media, на мобильниках это ни к чему). А за отображение отвечает CSS. Причем этому CSS прекрасно хватает двух уровней вложенности («таблица» и «ячейки»), промежуточную обертку (аналог tr) он умеет делать сам (тот самый анонимный бокс).

      А какие преимущества у HTML-таблицы перед таблицеподобным CSS-оформлением, не считая поддержки ископаемых IE?

        1. Там, где visibility: collapse для столбцов вообще работает, оно работает для всего с display: table-column (и к сожалению, с кроссбраузерностью у него всё достаточно плохо, в «вебкитятах» таблицам вообще хронически не везет).

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

  2. Если честно, не удалось воспроизвести указанную проблему. Думаю, её уже пофиксили. Скорее всего, внутри флекс-элмента создаётся анонимный строчный бокс, которф

  3. Ну вот, я, канешна, понимаю, что за базар нада отвечать, но всё же, иногда хорошо бы иметь возможность редактировать свои сообщения :) Мысль важнее слов :-)
    Так вот, скорее всего, для строчного контента создаётся анонимный инлайновый бокс, который входит в анонимный флекс-бокс. Думаю как-то так :)
    По крайней мере, вот этот пример (см. первую колонку), показывает что когда мы в явном виде задаём спаны, то они, похоже, оборачиваются в анонимную флекс-обёртку.
    Ну а так флекс-элементы будут идти друг за другом словно строчки (я имею в виду flex-direction: column на 16ой строчке, что же касается inline-flex’a на 13ой строчке, то думаю если мы назначим просто display-flex ничего не изменится — всё равно родитель будет воспринимать этот элемент не как инлайновый, а как флекс).

    1. У меня проблема в первом примере из статьи

      выделенные жирным слова слиплись с предыдущими, как будто перед ними нет пробела, а цифра «4» в предпоследней ячейке заняла целую строку

      воспроизводится даже в Chrome Canary 54 и Firefox Nightly 51. И, насколько я могу судить, так и должно быть по стандарту.

      Явные спаны в родителе с display:*-flex не «оборачиваются», а сами превращаются во флекс-элементы. В вашем первом примере между ними нет пробела, поэтому при flex-direction: row на первый взгляд разницы с поведением строчных элементов не видно (и так, и эдак элементы идут друг за другом в строке вплотную). Но разница станет заметна, если добавить пробелы и поэкспериментировать с разной длиной содержимого в этих спанах, чтобы они начали переноситься на новую строку.

      для строчного контента создаётся анонимный инлайновый бокс, который входит в анонимный флекс-бокс

      Наверное, можно сказать и так. Но фокус в том, что этот флекс-элемент не выпускает свое строчное содержимое за свои границы. И не впускает чужое. На другую строку он пересносится только весь целиком, и этим отличается от привычного поведения строчных элементов (см. первый пример статьи с жирной цифрой 4).

      1. Явные спаны в родителе с display:*-flex не «оборачиваются», а сами превращаются во флекс-элементы. В вашем первом примере между ними нет пробела, поэтому при flex-direction: row на первый взгляд разницы с поведением строчных элементов не видно (и так, и эдак элементы идут друг за другом в строке вплотную).

        Мы на разное смотрим?
        В первой колонке, как Вы и говорите два спана превратились во флекс-элемент и расположились рядом, два столбика по центру.

        Вообще, Вы правы, я невнимателен, всё там прекрасно воспроизводится. Просто я хотел сказать, что ЕСЛИ во флекс-контейнере нет вложенных тегов (я почему-то упустил что встроенные теги легко могут потребоваться для оформления), то всё строчное содержимое радостно вместится в виртуальный флекс-бокс. Если есть — то каждый анонимный и не анонимный строчный бокс будет в разных контейнерах и строчное форматирование поплывёт, Вы правы. И инлайн-флекс не помоможет пробелы будут игнорироваться, да и другие артефакты о которых Вы говорите возможны…
        Правда, вопрос, насколько такая ситуация актуальна. Ведь содержащий бокс и так будет растянут самым высоким элементов из отцентрованных. Но вообще, наверное, такая ситуация, когда это скажется, возможна…

        1. В статье как раз и приведена вполне жизненная, по-моему, ситуация, где эта задача актуальна: выровненные по высоте колонки с отцентрированным по вертикали произвольным содержимым. По сути, в общем-то, и вправду табличка из одной строки:)

  4. Я попробовал использовать flex и inline-table.

    Так как запретов и антисемантики в этом нет, это решение даже костылём назвать нельзя.

    Итог — элементы прекрасно центрируются, не нужно обёртки добавлять.

  5. Кхм, господа, а justify-content: center к .nav li применить не пробовали ? Или я что-то совсем на ночь не соображаю или выходит вполне себе вертикальное выравнивание. justify-content ведь — это выравнивание по главной оси, а из-за flex-direction: column главной стала как раз вертикальная.

    1. Так с align-*: center при flex-flow: row wrap для того же .nav li оно тоже центрируется. Проблема в том, чтобы не просто отцентрировать, но и сохранить дефолтное строчное поведение элементов <b> в контенте.

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

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

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