Новый display:flow-root — не clearfix, но что это и зачем?
Новые значения свойства display из модуля CSS Display 3-го уровня (за развитием которого мы следили с позапрошлого года) потихоньку проникают в браузерную реальность. Первая ласточка — display:contents — уже больше года поддерживается в Firefox, без всяких префиксов и флагов. Вторая ласточка «прилетит» вот-вот: в Firefox 53+ и Chrome 57+ появляется display: flow-root . И оно уже работает в ночных сборках и Canary соответственно.
Вокруг этого значения уже много путаницы. Надеюсь, это своего рода мини-ЧаВо поможет в этой путанице разобраться.
— Что дает display: flow-root ?
Кратко: заставляет блок создавать отдельный блочный контекст форматирования. Без побочных эффектов.
Долго: см. подробности ниже.
— Что такое контекст форматирования?
Кратко: штука, заставляющая CSS делать раскладку. Кроме шуток: именно так, почти дословно, определялось это понятие в ранних редакциях спецификации CSS Display 3.
Долго: сейчас контекст форматирования определяется в спецификации так: «среда, в которой размещается набор связанных между собой контейнеров». Правила этого размещения в разных контекстах форматирования бывают разные. Например, во флекс-контейнере действуют контекст форматирования флексбоксов, и правила размещения дочерних элементов (направления главной и поперечной осей, будет ли флексбокс многострочным и т.д.) определяются свойством flex-flow (и его составляющими). У грид-контейнеров свой контекст форматирования, и правила размещения у него свои. В табличном контексте форматирования в обязательном порядке выстраивается структура, эмулирующая структуру HTML-таблицы (table/table-row/table-cell), если какой-то уровень пропущен, браузер достравивает вместо него анонимные боксы. И т.д.
Но чаще всего мы имеем дело с двумя контекстами форматирования: инлайновым и блочным. Слова (и прочие вещи, встраиваемые прямо в текст — иконки, поля форм и т.д.) собираются в строки, строки «пакуются» в этакие прямоугольные «ящики» — блоки, которые ставятся один к одному «штабелями». Для нас привычно, что строки идут горизонтально слева направо, а блоки — вертикально сверху вниз, но бывают и другие варианты. Собирательно такой порядок вещей называют «нормальным потоком», «потоковой раскладкой» или просто «потоком» (flow по-английски).
— А зачем делать этот контекст отдельным?
В блочном контексте форматирования встречаются не только обычные блоки, но и плавающие (наши любимые float-ы). По спецификации, плавающие блоки не влияют на обычные, но влияют на их инлайновое (текстовое) содержимое — заставляют контейнеры строк («line boxes») ужаться. Т.е. границы обычных блоков для плавающих ничего не значат — они запросто накладываются на блоки и проходят сквозь них, хоть внутрь, хоть наружу, хоть навылет. А вот текст накладываться на них не может и вынужден обтекать их снаружи, втискиваясь в оставшееся место. Вот примерная иллюстрация (оффлайновая:), как это может происходить:
Поскольку float-ы придуманы именно для обтекания врезок текстом, а не для горизонтальной раскладки блоков, это поведение — «не баг, а фича»: текст будет обтекать врезку ровно столько, сколько нужно, не дожидаясь конца блока или абзаца, в верстке не будет некрасивых дырок. Но из него напрямую следует «недержание» float-ов обычными блочными контейнерами, в которых текста недостаточно или вообще нет. Его-то и приходится «лечить» хаками: либо добавлять в конец контейнера (псевдо)элемент с clear (это clearfix и его варианты), либо делать так, чтобы float-ы внутри контейнера и блоки снаружи принадлежали разным контекстам форматирования. На блоки из чужих контекстов float-ы не «наезжают».
И еще одна «фича» CSS для сплошного текста, оборачивающаяся проблемами в чем-то более сложном — легендарное «схлопывание» margin-ов. Опять же, из обычного блока внешние отступы его дочерних элементов могут «вываливаться» наружу. А из блока, создающего отдельный блочный контекст — никогда.
У меня был еще старенький наглядный пример на эту тему.
— Так что, display: flow-root — это просто новый модный clearfix?
Нет. Это стандартная замена тому, что раньше делалось через overflow:hidden, display:table/table-cell и еще более изощренные хаки.
— А в чем разница?
При clearfix плавающие блоки и потомки с выпадающими margin-ами остаются в том же контексте форматирования. А в этих способах — нет. Лучше всего увидеть и «пощупать» это на интерактивных примерах, типа такого или в CodePen чуть ниже. Можно посмотреть и сводную таблицу сравнения.
— А вот Рэйчел Эндрю пишет, что это — конец хакам с клирфиксами…
Да, хакам конец, потому что у проблемы теперь есть стандартное решение. Но при всём моем огромном уважении к Рэйчел здесь она, по-моему, слегка переупрощает.
Я форкнул пример из ее статьи и подбавил туда еще элементов, чтобы наглядно подчеркнуть разницу в поведении каждого подхода (и еще пару бонусов для внимательных читателей):
Так что в общем случае, как верно заметил там в комментариях Тьерри Кобленц, нельзя просто взять и заменить display: flow-root на clearfix и наоборот. Надо с умом смотреть по ситуации и тестировать!
— Почему этот flow-root не назвали как-нибудь более понятно, скажем, float-container?
Кратко: как минимум, потому что он не только про float-ы. А понятие «поток» (flow) одно из ключевых в CSS, из того, чего стыдно не знать;)
Долго: суть этого значения вообще не связана с плавающими блоками, и даже с блоками вообще. Суть в том, чтобы элемент мог «решить», как ему поступить со своим содержимым: продолжить для него родительский контекст форматирования или создать свой собственный.
В модуле CSS Display 3 свойство display сделали составным: одна часть отвечает за внешнее поведение блока (по отношению к соседям), другая — за внутреннее (правила размещения дочерних элементов). Еще при первом знакомстве с этим модулем мы заметили, что большинство значений display однозначно определяют и то, и другое. То есть однозначно соответствуют конкретным парам новых значений, отвечающих за оба контекста форматирования — внешний, в котором «живет» сам элемент, и внутренний, который управляет его потомками. Все такие элементы создают этот второй контекст автоматически, волей-неволей.
И лишь два значения display в принципе могут продолжать внутри себя тот же контекст форматирования, в котором находятся сами. Это inline и block. То, из чего состоит поток (flow). Вот W3C и назвали такое поведение элемента, когда внешний поток продолжается внутри него, «втекает» туда — flow. А такое, когда внутри элемента поток как бы начинается заново, независимо от внешнего — flow-root (букв. «корень потока», т.е. его начало).
Для элемента инлайнового уровня первый вариант (составное значение inline flow) соответствует обычному inline, а второй (inline flow-root) — старому доброму inline-block. А вот для блочного элемента раньше был только обычный block, соответствующий составному block flow. Сделать блок началом нового контекста удавалось лишь побочным эффектом от чего-то другого — т.е. хаком. Теперь же для этого есть стандартное значение (соответствующее составному block flow-root).
К сожалению, сами составные значения display браузеры еще не поддерживают. Ждут, когда эта часть спецификации «дозреет». Всё-таки, просто добавить одно-два значения для свойства — это одно, а вот менять его логику и ради этого переделывать CSS-парсер — совсем другое…
— Всё равно не нравится мне название flow-root. Может, еще не поздно поменять его на что-то более «человеческое»?
Можно попытаться. Только обоснуйте получше, ведь без реальной нужды менять уже реализованное значение никто не станет.
Добавлено 02.02.2017. Уже нельзя: рабочая группа приняла решение оставить название «как есть», в таком виде и перевести спецификацию в статус кандидата в рекомендации. Добавлено 15.02.2017. Вопрос о переименовании закрыт.
— Как быть, если я хочу применить модный клирфи… то есть отдельный контекст, скажем, к флекс-контейнеру? Сделать «display: flex flow-root» ведь нельзя. Понадобится добавочная обертка?
Нет, не понадобится. Это попросту не нужно. Проблемы с «выпадением» float-ов и margin-ов возникают только в потоке, т.е. для обычных блоков. Элементы с другими правилами форматирования изолируют свои контексты автоматически. К тому же в самом флекс-контейнере никаких float-ов и не бывает: даже если у его дочерних элементов и стоял float, «магия» флексбоксового контекста оказывается сильнее и превращает их в обычные флекс-элементы, наравне с прочими. А уже внутри этих флекс-элементов — свой собственный изолированный контекст, из которого тоже ничего никуда не вываливается.
— Кстати, раз есть флексбоксы, зачем нам сейчас вообще все эти хаки с флоатами?
Верно подмечено! Действительно, именно для раскладки блоков такие хаки уже практически не нужны. Это раньше приходилось страдать от издержек неподходящих инструментов, потому что других просто не было. А что каких-то 3% браузеров годные инструменты не понимают — так стоит ли убиваться ради мегакрасоты в этих динозаврах, может, важнее, чтобы сайты/интерфейсы быстро грузились и не не падали под тяжестью лишних костылей и полифилов, а «спартанский» внешний вид — наименьшая проблема для их привыкших к трудностям пользователей? Так что со временем эти «клирфиксы» неизбежно канут в историю. Кто сегодня вспоминает IEшный hasLayout (кстати, тоже в каком-то смысле способ создания блочного контекста форматирования:)?
Но бывает, когда нужно использовать float по прямому назначению (для обтекания текстом), текста этого кот наплакал, а блок с таким содержимым должен быть цельным, монолитным и независимым от окружения. Например, какой-нибудь рекламный или новостной анонс. И чтоб жизнь малиной не казалась, из этого блока должна «выезжать» всплывающая подсказка или еще какая-нибудь дизайнерская красота, так что overflow — не вариант. В таких вот случаях новое стандартное решение может очень выручить.
— А что делать сейчас, пока массовые браузеры лишь ждут поддержки flow-root? Можно ли его заполифилить?
К сожалению, прямой и универсальной замены среди существующих решений нет (как мы уже видели). У каждого из них свои ограничения и недостатки, и надо внимательно смотртеть по конкретной задаче, в каком случае что критичнее. Где-то подойдет overflow, где-то — inline-block/table/table-cell с явным указанием размеров (иначе они получат размер по контенту). Если контейнер — элемент fieldset, вам повезло: с ним почти ничего делать не надо, он создает новый блочный контекст по умолчанию. И по возможности используйте для раскладки блоков не float-ы, а флексбоксы и гриды (ну и CSS-таблицы зря со счетов не сбрасывайте), чтобы вообще не доводить дело до хаков.
Впрочем, можно порыться в спецификациях (напр. введя в поисковик «’block formatting context’ site:drafts.csswg.org» — чтоб искать конкретную фразу и именно среди актуальных редакторских наработок) и посмотреть, вдруг в них найдется что-то, о чем мало кто вспоминает, но подходящее именно вам? Например, новый блочный контекст форматирования создают многоколоночные контейнеры — причем даже при column-count:1. Или вот чуть более сырой, но на первый взгляд еще более «близкий к теме» модуль CSS-изоляции, уже работающий во всём «хромообразном».
Так что следите за новинками, не бойтесь эксперименировать, и да пребудет с вами CSSила! =)