О «призраках» в CSS и борьбе с ними: анонимные боксы

Эту спецификацию следует читать так же, как и все другие спецификации. Сначала от корки до корки несколько раз. Потом в обратном направлении, минимум один раз. Потом нужно прочитать ее, выбирая случайные разделы в оглавлении и переходя по всем перекрестным ссылкам.

Стандарт HTML5

Для спецификаций CSS этот совет актуален вдвойне: при каждом прочтении открывается очередная тайна. Как-то раз, расследуя детективную историю о пропавшем контексте форматирования, мы ненароком «открыли периодический закон» для свойства display. В этот раз мы столкнемся с мистикой.

…Они появляются из ниоткуда. Они бестелесны. Их не может поймать даже DOM-инспектор. Они своенравны: иногда они незримо приходят на помощь, но могут и довести до слез своими проказами. У них нет имени. Лишь немногие владеют тайной, позволяющей повелевать ими…

Сегодня вы тоже узнаете эту тайну. Но сначала давайте вызовем парочку их.

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

Код в этом примере полностью валиден. Вложенность DOM-элементов верная. Откуда же взялась таинственная белая фигура?

Знакомьтесь: анонимные боксы

На фразе «у них нет имени» вы наверняка догадались, что речь пойдет об безымянных, или анонимных CSS-боксах. Конечно, они — не баг, не глюк и не полтергейст, а документированная особенность стандарта CSS, так что бояться их не надо:). Но вот знать и понимать логику этого стандарта, чтобы не пугаться «внезапно» появляющихся «аномалий», а осознанно пользоваться ими в своих целях (и не допускать их появления, если они этим целям противоречат) — на мой взгляд, полезно.

Что происходит по стандарту в предыдущем примере?

Посередине у нас блочный бокс, образованный внутренним span-ом. Значит, его окружение должно представлять собой блочный конекст форматирования, поскольку ни в чем другом блоки не живут. Значит, соседями нашего блочного бокса могут быть только другие блочные боксы.

Но наш блочный бокс сделал невозможное и оказался внутри элемента с display:inline. Если бы в строчном элементе был текст, он оказался бы разорван на две половины — до блока и после (можете навести на пример и посмотреть). Т.е. получились бы два строчных бокса, каждый — в собственном контейнере строки. И раз дело происходит в блочном контексте, каждый из этих контейнеров строки пришлось бы обернуть в тот самый анонимный блочный бокс.

А если текста в строчном элементе нет? Спецификация предусматривает лишь одно исключение (кстати, по ссылке с якорем #phantom-line-box — «призрачный контейнер строки»), и наш случай в него не попадает:

Контейнеры строки, не содержащие ни текста, ни сохраненных пробелов, ни строчных элементов с ненулевыми внешними/внутренними отступами или рамками, ни другого контента в потоке (напр. изображений, инлайн-блоков и инлайн-таблиц) и не оканчивающиеся сохраняемым переводом строки, должны восприниматься как контейнеры строки нулевой высоты при определении положения любых элементов внутри них, и как несуществующие для любых других целей.

У наших половин строчного элемента нет текста, но есть padding-и. Они и не дают «призрачным» строкам схлопнуться до полной невидимости, держат их высоту. Эти половины строчного элемента мы и видим как «призрачные» фигуры!

Еще бывает, что из-за крошечной картинки или инлайн-блока нулевой высоты рядом с блоком появляется пустое пространство высотой с целую строку:

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

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

Продвинутый CSSпиритизм

В старину призраков вызывали с помощью «столоверчения», по-английски — «table-lifting». В CSS тоже всё, где вертится table, так и приманивает боксы-призраки.

Не будем опять заводить спор о том, хороши или плохи CSS-таблицы вообще, влияют ли они на поисковики, доступность контента и карму верстальщика — статья не о том. Подойдем с другого конца: с модных флексбоксов.

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

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

Но в Firefox долгое время так не выходило. Потому что по стандарту (прежней его редакции — см. ниже) на самом деле мы оборачивали флекс-контейнером не три отдельных ячейки. А одну призрачную CSS-таблицу, содержащую одну призрачную строку с тремя CSS-ячейками (уже реальными). Таковы правила того самого «неуловимого» табличного контекста форматирования. И флекс-элементом становилась эта таблица (точнее, ее «бокс-обертка» — см. ниже), а не отдельные ячейки. Занятно, что в Chrome флекс-элементами оказываются именно ячейки — но до недавнего времени это был баг.

UPD 11.01.2016: в связи с обновлением спецификации флекбсоксов информация из предыдущего абзаца устарела, правильным поведением теперь считается поведение Chrome (ячейки непосредственно становятся флекс-элементами). Но это именно исключение, в общем случае анонимные обертки вокруг ячеек CSS-таблиц по-прежнему создаются, и дальнейшая информация по-прежнему актуальна.

UPD 26.10.2016: с 47-й версии Firefox тоже ведет себя как Chrome, по новой спецификации.

К счастью, правила создания анонимных табличных боксов описаны в одном месте спецификации — специальном разделе, и описаны хоть и типичным для спецификаций тяжеловесным языком, но достаточно понятно.

Предельно упрощенная выжимка оттуда: вокруг любой группы соседних ячеек, не обернутых в строку, создается анонимная строка; вокруг группы строк (в т.ч. анонимных), не обернутых в таблицу, создается анонимная таблица, и эта таблица «для верности» оборачивается в еще один бокс — так называемый table wrapper box, «бокс-обертка таблицы», поведением похожий на блок. А потомки строки, не являющиеся ячейкой, оборачиваются в анонимную ячейку.

Последнее бывает полезно. Например, блоки «Популярное» на этом сайте — если вы читаете страницу на большом экране, они будут справа как раз примерно на уровне этого абзаца — сверстаны как двухколоночная таблица, благодаря чему числа ровно выравнены независимо от разрядности, хотя display:table-cell задан лишь первым ячейкам в строке! Но остальное чаще ограничивает, ведь «призрачной» таблице, вынужденно достроенной браузером, нельзя задать ни margin, ни border, ни ширину — только наследуемые свойства, косвенно, через родителя (такие, как цвет, шрифт, или — менее очевидно, но часто полезно — border-spacing).

Как же быть, чтобы для старых браузеров работала табличная раскладка, а для новых — флексовая? «Модный и молодежный» вариант — воспользоваться директивой @supports. Правда, ее пока поддерживает меньше браузеров, чем флексбоксы, так что в некоторых браузерах будет работать CSS-таблица, хотя теоретически мог бы работать новый механизм. Зато не хак, а почти стандарт (кандидат в рекомендации!) и почти наверняка без накладок. Если знаете способ лучше — поделитесь!

А раньше, во времена Opera 11 и Safari 5, «разнобоя» между браузерами с табличными боксами было еще больше. Так что на всякий случай поведение анонимных боксов нужно особенно тщательно тестировать на кроссбраузерность.

Странники у «границы между мирами»

Где встречаются призраки (там, где они вообще встречаются, конечно — в легендах и фентези)? Чаще всего — «там, где миры соприкасаются», «там, где граница между мирами особенно тонка». В CSS роль таких «соприкасающихся миров», на стыке которых легче всего встретить бокс-«призрак», играют давно нам знакомые, но по-прежнему загадочные контексты форматирования.

Вернемся к примерам из начала статьи. Два первых примера объединяет «незапланированное» соседство блочного и инлайнового контекстов: в первом блок «ворвался» в инлайновое «царство», приведя с собой два неявных блока, во втором — инлайн-блок невольно создал маленький инлайновый «островок», спутав карты окружающему блочному контексту и опять же вынудив его обратиться к «мистической» части стандарта. Помните, что значения display с дефисом как раз часто создают «границу между мирами»? У инлайн-блока внутри блочный контекст, а снаружи инлайновый, у ячейки таблицы — внутри блочный, снаружи табличный… Поэтому всегда будьте особенно внимательны с таким значениями display! На мой взгляд, полезно уже сейчас думать о них в категориях отдельных свойств display-outside и display-inside из нового черновика: как только они различаются — готовься к сюрпризам. А лучше — с самого начала следить, чтобы по возможности все CSS-боксы явно попадали в контекст форматирования, соответствующий их display-outside. И не сводить без крайней нужды блоки и inline-что-либо в одном контейнере.

Иногда такие примеры приводят как аргумент против блочных ссылок из HTML5: дескать, с этими новыми стандартами разработчики забыли древнюю мудрость типа «нельзя пихать блочное в строчное» — вот, мол, веб и катится… к привидениям. И в этом есть доля правды — пытаться вложить блочные CSS-боксы в строчные действительно… не стоит. Вот только HTML5 и вообще теги тут ни при чем. HTML-, точнее, DOM-элементы могут быть любые, которым такое сочетание разрешает спецификация (см. эпиграф:). Но вот CSS — всецело зона ответственности верстальщика.

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

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

1 Комментарий

  1. NMitra

    Спасибо, познавательно!

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

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

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

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