Когда флексбоксы не всесильны, или сюрприз тёмной стороны CSS
Сегодня, когда флексбоксы поддерживаются в 93% браузеров, многие видят в них панацею для верстки. И неспроста: это первый модуль CSS специально для раскладки блоков, многие когда-то сложные задачи с ним решаются буквально парой строчек. Есть даже целая галерея таких решений (ее автор Филип Уолтон знаком нам по систематизации браузерных багов с флексбоксами). Кажется, что этой новой чудесной технологии подвластно всё. Так что легко понять недавнее удивление известного веб-дизайнера, разработчика и популяризатора Уэса Боса в твиттере:
Неужели это невозможно с флексбоксами? Как мне заставить флекс-элементы растягиваться, но выравнивать свой контент по вертикали? Вот код: codepen.io/wesbos/pen/vOxWbq
Казалось бы, элементарная задача. Всего одна строка флекс-элементов, в самих элементах ничего хитрого, обычное строчное содержимое. И по отдельности требования Уэса реализуются проще некуда. Вертикальное центрирование — одной строчкой кода: 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. Это тоже может быть интересно:
Подскажите, а где про проблемы CSS-таблиц почитать?
Основная проблема, с которой пришлось столкнуться — ячейки таблицы не везде поддерживают position: relative. И, как следствие, чтоб прижать что-то в ячейке с контентом к нижней границе, нужно придумывать костыли.
Это когда было? Уже давно это пофикшено во всех браузерах. Последний был Firefox, но в версии 32-й (или 33-й, уже точно не помню) это было исправлено.
А вообще, было бы интересно увидеть живой пример, где именно это не работает?
Охотно поверю. Столкнулся с этим почти 2 года назад, с тех пор не проверял пофикшено ли.
Главная проблема табличного отображения в том, что спецификация для него очень запутанная, браузеры трактуют ее достаточно вольно, и спецификация мало что от них требует:). Для
table-layout:fixed
(как в этом примере) ситуация получше, но сюрпризы всё равно бывают. Несколько лет назад об этом была занятная статья на Хабре с поучительным разбором. С тех пор ситуация в браузерах чуть улучшилась, но некоторые проблемы остались: Хром так и не научилася делать ячейки нулевой ширины и применять фон кtable-row
, Firefox, хоть и исправил давние противные баги с позиционированием содержимого ячеек и самих ячеек, неправильно представляет CSS-таблицы скринридерам (как будто это настоящие таблицы), а в IE и проблема с позиционированием (см. в конце) еще актуальна. Но как видим, бывают ситуации, когда эти проблемы не столь существенны, и CSS-таблицы решают задачу самым прямым и логичным способом.UPD: со времени предыдущего коммента Firefox исправил баг с представлением CSS-таблиц скринридерам. И, похоже, спецификация для табличного форматирования начинает понемногу оживать:)
Так видимо не кошерно, но зачем лечить гланды через ухо?
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
В смысле, почему не тегами table/tr/td? Именно потому что, как вы сказали про гланды: нам не нужно, чтобы скринридеры для слепых зачитывали это меню как «Таблица из одного ряда, первая ячейка…», не нужно загромождать память дополнительными DOM-методами типа insertRow и т.п. Всё, что нам нужно — это придать этой штуке таблицеподобное отображение (и то, наверное, не везде и не всегда, а лишь на широком экране, для соответствующего @media, на мобильниках это ни к чему). А за отображение отвечает CSS. Причем этому CSS прекрасно хватает двух уровней вложенности («таблица» и «ячейки»), промежуточную обертку (аналог tr) он умеет делать сам (тот самый анонимный бокс).
А какие преимущества у HTML-таблицы перед таблицеподобным CSS-оформлением, не считая поддержки ископаемых IE?
Атрибуты colspan и rowspan, visibility: collapse; для col.
Там, где
visibility: collapse
для столбцов вообще работает, оно работает для всего сdisplay: table-column
(и к сожалению, с кроссбраузерностью у него всё достаточно плохо, в «вебкитятах» таблицам вообще хронически не везет).Вот
colspan
/rowspan
, да, пока средствами CSS никак не воспроизвести. Но часто ли они нужны в задачах, где требуется лишь таблицеподобное оформление, не для табличных данных? Впрочем, мне кажется, что потребность в продвинутых табличных функциях (скрытие/показ столбцов, сортировка, фиксация заголовков и т.д.) сама по себе указывает на то, что данные действительно табличные:)Удалило код -> обычной таблицей и о чудо все везде работает )
Если честно, не удалось воспроизвести указанную проблему. Думаю, её уже пофиксили. Скорее всего, внутри флекс-элмента создаётся анонимный строчный бокс, которф
Ну вот, я, канешна, понимаю, что за базар нада отвечать, но всё же, иногда хорошо бы иметь возможность редактировать свои сообщения :) Мысль важнее слов :-)
Так вот, скорее всего, для строчного контента создаётся анонимный инлайновый бокс, который входит в анонимный флекс-бокс. Думаю как-то так :)
По крайней мере, вот этот пример (см. первую колонку), показывает что когда мы в явном виде задаём спаны, то они, похоже, оборачиваются в анонимную флекс-обёртку.
Ну а так флекс-элементы будут идти друг за другом словно строчки (я имею в виду
flex-direction: column
на 16ой строчке, что же касается inline-flex’a на 13ой строчке, то думаю если мы назначим просто display-flex ничего не изменится — всё равно родитель будет воспринимать этот элемент не как инлайновый, а как флекс).У меня проблема в первом примере из статьи
воспроизводится даже в Chrome Canary 54 и Firefox Nightly 51. И, насколько я могу судить, так и должно быть по стандарту.
Явные спаны в родителе с
display:*-flex
не «оборачиваются», а сами превращаются во флекс-элементы. В вашем первом примере между ними нет пробела, поэтому приflex-direction: row
на первый взгляд разницы с поведением строчных элементов не видно (и так, и эдак элементы идут друг за другом в строке вплотную). Но разница станет заметна, если добавить пробелы и поэкспериментировать с разной длиной содержимого в этих спанах, чтобы они начали переноситься на новую строку.Наверное, можно сказать и так. Но фокус в том, что этот флекс-элемент не выпускает свое строчное содержимое за свои границы. И не впускает чужое. На другую строку он пересносится только весь целиком, и этим отличается от привычного поведения строчных элементов (см. первый пример статьи с жирной цифрой 4).
Мы на разное смотрим?
В первой колонке, как Вы и говорите два спана превратились во флекс-элемент и расположились рядом, два столбика по центру.
Вообще, Вы правы, я невнимателен, всё там прекрасно воспроизводится. Просто я хотел сказать, что ЕСЛИ во флекс-контейнере нет вложенных тегов (я почему-то упустил что встроенные теги легко могут потребоваться для оформления), то всё строчное содержимое радостно вместится в виртуальный флекс-бокс. Если есть — то каждый анонимный и не анонимный строчный бокс будет в разных контейнерах и строчное форматирование поплывёт, Вы правы. И инлайн-флекс не помоможет пробелы будут игнорироваться, да и другие артефакты о которых Вы говорите возможны…
Правда, вопрос, насколько такая ситуация актуальна. Ведь содержащий бокс и так будет растянут самым высоким элементов из отцентрованных. Но вообще, наверное, такая ситуация, когда это скажется, возможна…
В статье как раз и приведена вполне жизненная, по-моему, ситуация, где эта задача актуальна: выровненные по высоте колонки с отцентрированным по вертикали произвольным содержимым. По сути, в общем-то, и вправду табличка из одной строки:)
Я попробовал использовать flex и inline-table.
Так как запретов и антисемантики в этом нет, это решение даже костылём назвать нельзя.
Итог — элементы прекрасно центрируются, не нужно обёртки добавлять.
А можно увидеть это решение применительно к примеру из статьи?
Кхм, господа, а justify-content: center к .nav li применить не пробовали ? Или я что-то совсем на ночь не соображаю или выходит вполне себе вертикальное выравнивание. justify-content ведь — это выравнивание по главной оси, а из-за flex-direction: column главной стала как раз вертикальная.
Так с align-*: center при flex-flow: row wrap для того же .nav li оно тоже центрируется. Проблема в том, чтобы не просто отцентрировать, но и сохранить дефолтное строчное поведение элементов <b> в контенте.