Когда флексбоксы не всесильны, или сюрприз тёмной стороны 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. Это тоже может быть интересно:

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

  1. Алексей

    Подскажите, а где про проблемы CSS-таблиц почитать?

    1. BETEPAH

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

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

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

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

        1. BETEPAH

          Охотно поверю. Столкнулся с этим почти 2 года назад, с тех пор не проверял пофикшено ли.

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

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

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

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

  2. Svetlana

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

    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. SelenIT (Автор записи)

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

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

      1. NMitra

        Атрибуты colspan и rowspan, visibility: collapse; для col.

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

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

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

  3. Svetlana

    Удалило код -> обычной таблицей и о чудо все везде работает )

  4. Алексей

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

  5. Алексей

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

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

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

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

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

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

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

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

      1. Алексей

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

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

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

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

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

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

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

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

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