Новый 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 чуть ниже. Можно посмотреть и сводную таблицу сравнения.

— А вот Рэйчел Эндрю пишет, что это — конец хакам с клирфиксами…

Да, хакам конец, потому что у проблемы теперь есть стандартное решение. Но при всём моем огромном уважении к Рэйчел здесь она, по-моему, слегка переупрощает.

Я форкнул пример из ее статьи и подбавил туда еще элементов, чтобы наглядно подчеркнуть разницу в поведении каждого подхода (и еще пару бонусов для внимательных читателей):

See the Pen Способы создания БКФ by Ilya Streltsyn (@SelenIT) on 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ила! =)

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

8 Комментарии
  1. Аноним

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

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

      Есть в интерактивном примере и статье, для которой этот пример изначально делался, причем в двух вариантах (с автоматической шириной, в чем и состоит главное отличие от обычного display:block, и с хаком для его нивелировки). Но согласен, надо было добавить в более явном виде. Готово!

  2. Лев Солнцев

    Несмотря на свою подробность, статья почему-то обходит способ, известный как «new micro clearfix»:

    .cf:before,
    .cf:after {
    content: " ";
    display: table;
    }

    .cf:after {
    clear: both;
    }

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

    Всё это благодаря display: table. Впрочем, прямо в спецификации сказано, что display: table-cell создаёт тот же flow-root (анонимная ячейка по спецификации создаётся автоматически).

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

      Да, этот вариант — наверное, лучший среди «классических клирфиксов» (основанных именно на действии clear, без создания отдельного контекста). Но он не обладает главным чудесным свойством отдельного контекста и не решает общей их проблемы — возможного влияния посторонних float-ов, находящихся выше в потоке. Поэтому в статье я сосредоточился на способах с созданием отдельного контекста, тем более что новое стандартное решение относится именно к этому классу. Ну и еще я уже успел задействовать ::before в примерах для выступающего треугольника (из лени менять разметку:), поэтому методу, использующему ::before для другого, места в примерах уже не нашлось…

      Но вот прямо сейчас я дописываю своего рода приложение к этой статье, где этот метод уже упомянут:)

      UPD: вот это приложение:)

  3. nbsp

    Странно что такую штуку решили добавить в «display: что-то», а не в другое, либо новое свойство. Для некоторых случаев придется создавать новый враппер. А если нужен второй враппер, то можно использовать и почти универсальный overflow: hidden (из блока редко что-то выпирает) и лишний div не создавать.

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

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

      Выпирающее из блока что-то (декоративная лента, тень, всплывающая подсказка и т.п.) — не такая уж редкая задача. Так что чем больше вариантов решения, тем лучше. Тем более если это стандартное решение.

      А чем плохо, что это значение display? Если другие значения display тоже создают БКФ, но меняют поведение блока — почему бы не быть значению, создающему БКФ, но в остальном сохраняющему его?

  4. nbsp

    Возможно, не правильно выразил мысль.

    Для inline-block эта штука не подойдет, придется создавать доп блок.
    И в случае когда нужно скрывать и показывать блок (по умолчанию, display:block) (show(), hide()) придется либо создавать внутренний блок с новым свойством, либо предусматривать логику в js.

    А было бы проще, если бы это было новое свойство, либо новое значение для другого уже существующего свойства. Скорее всего это технические особенности. Просто со стороны это выглядит как «display:block с фишкой», а не новые режимы content и revert, например.

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

      Всё равно не понимаю. Причем тут inline-block? Он уже создает новый блочный контекст и совсем иначе ведет себя снаружи. По-моему, случаи, когда по стандарту нужен inline-block, а когда — flow-root, не пересекаются. Или речь про контейнер инлайн-блоков? Но и там не вижу проблемы — инлайн-блоки и так не выпадают из контейнера, в отличие от float-ов…

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

      В JS, да, просто менять none на block и обратно не получится. Как и для строк таблиц (с display:table-row) и подобного. Если верить документации jQuery, после hide()/show() элемент возвращает то значение display, который был до этого, так что в этом случае проблем не будет. Вообще раньше хорошим тоном считалось вместо прямой смены display добавлять/убирать класс типа .hidden{display:none}. Есть еще вариант el.display="".

      Впрочем, как раз для решения этой проблемы («отмены display:none») в том же новом модуле CSS Display хотели ввести отдельное свойство (box-supress или display-or-not), чтобы оно отвечало за то, отображать ли элемент вообще, а обычный display — только за то, как именно отображать. Правда, сейчас его отложили на 4-й уровень, так что в браузерах оно появится и вовсе нескоро…

Добавить комментарий для Аноним Отменить ответ

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

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

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