Каскадные Таблицы Стилей

индекс
323,31

Float'омагия: пробуем не «плавать» в спецификации, чтобы не утонуть в потоке

Пример действия float IRLНедавняя статья про float-ы и реакция хабрасообщества на нее показали, что «сладкая парочка» CSS-свойств float и clear, несмотря на свою давнюю и колоссальную популярность в верстке, для многих остается чем-то загадочным и таинственным. Однако затронута в ней оказалась лишь верхушка айсберга, теряющегося в пучине мутных вод загадочной спецификации CSS 2.1. А есть и «подводные рифы», в спецификации не описанные. Не желаете ли продолжить увлекательное погружение в этот мир удивительных открытий и добраться до самого дна, полного сокровищ? Если да, то…

1. Немного азбучных основ: с чем и как взаимодействуют float-ы


Все знают, что блоки с float «вырываются/выпадают из нормального потока», но мало кто может толком сказать, что же это за «нормальный поток». Даже спецификация. В самом начале раздела 9.3 первый пункт гласит:
В CSS 2.1 нормальный поток включает блочное форматирование блочных боксов, внутристрочное форматирование внутристрочных1 боксов и относительное позиционирование тех и других.

Пример неправильного использования float IRLДальше упоминаются понятия блочного и внутристрочного контекстов форматирования. Четких определений этих контекстов мне найти так и не удалось (еще одна тайна?), зато сказано, что элемент может принимать участие только в одном из этих контекстов.

Со стороны всё как будто несложно. Бокс, грубо — это прямоугольник. Прямоугольники бывают двух типов: большие, выстраиваемые друг за дружкой по вертикали (сверху вниз), как штабель из ящиков — это блочные боксы (блоки). И маленькие, выстравиваемые друг за дружкой цепочкой по горизонтали, слева направо (или справа налево), ряд за рядом, как кладка из кирпичей — это внутристрочные боксы («инлайны»). В первом случае мы наблюдаем блочный контекст форматирования (где участвуют сами блоки), а во втором — внутристрочный (разбиение текста на строки внутри блока).

С добавлением float-ов начинаются чудеса. Начиная с определения:
В плавающей модели бокс сначала размещается согласно нормальному потоку, затем убирается из потока и смещается вправо или влево на максимально возможное расстояние. Контент может обтекать float вдоль одной стороны.
Чувствуете парадокс: бокс из потока вынут — но на поток контента, тем не менее, влияет!

Пример взаимодействия float-ов, блоков и инлайнов IRLТак вот: оказывается, float-ы… полностью игнорируются блочными боксами (в пределах одного блочного контекста)! Их границы полностью проницаемы для них. Неважно, лежит ли float внутри блочного бокса или снаружи: в обоих случаях границы блочного бокса его «не чувствуют», он проходит сквозь них, как бесплотный дух. Блоки продолжают выстраиваться друг за другом вплотную, не обращая внимания на float-ы. Представить это трудно, это надо видеть (что-то наподобие примера).

А вот внутристрочные боксы присутствие float-ов очень даже чувствуют. В отличие от блочных боксов, они никогда не пересекаются границами с float-ом. Они — тот самый контент, которому положено «обтекать float вдоль одной стороны».

На странице float жмется в угол, образованный боковой границей контейнера и верхней границей текущей строки (строчного бокса). А идущие за ним внутристрочные боксы уже вынуждены становиться на свободное от него место — т.е. строчные боксы, соседствующие с float-ом, оказываются короче. А что делать. Ширина у контейнера не резиновая, вверху всё занято, и боковое пространство float успел занять раньше («кто первый встал, того и тапки»)…

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

2. Немного ясности о мутноватом clear-е


Что еще за «clear», что и от чего он «очищает», откуда такое название? Я сам долго этого не понимал, пока не вчитался в текст п. 9.5.2 спецификации:
Все значения [свойства clear], кроме 'none', потенциально вводят клиренс (clearance). Клиренс препятствует схлопыванию margin-ов и действует как промежуток над верхним margin-ом элемента. Он служит для «выталкивания» элемента по вертикали ниже float-а.

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

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

3. Сам себе блочный контекст


Первый же подраздел главы о нормальном потоке сообщает нам, что
Float-ы, абсолютно позиционированные элементы, контейнеры блоков, не являющиеся блочными боксами (напр. инлайн-блоки и ячейки таблиц), а также блочные боксы с 'overflow', не равным 'visible' (если это значение не распространяется на область просмотра) задают новый блочный контекст форматирования для своего содержимого.

Из этого, кроме прочего, неизбежно вытекае блочное поведение самих float-ов (как можно быть контейнером блоков, не будучи блоком?).

С потребительской точки зрения, элемент с отдельным блочным контекстом форматирования — прочный контейнер, удерживающий в себе всё непозиционированное содержимое со всеми «выступающими частями» (типа «вываливающихся» margin-ов и «торчащих» float-ов) и охватывающий его по габаритам. Можете еще раз взглянуть на демку и покликать по ссылке в последнем зеленом абзаце. В отличие от обычных блоков, чьи границы полностью прозрачны для float-ов, его границы «на замке» с обеих сторон — float ни изнутри не выскочит, ни снаружи не проскочит.

Как же спецификация предписывает вести себя такому блоку, если приходится разместить его рядом с float-ом? Сюрприз: оказывается… никак!
… Если необходимо, реализациям следовало бы придавать таким элементам клиренс, размещая их ниже предшествующих float-ов. Они могут даже делать внешние габариты элемента у́же, чем предписано разделом 10.3.3. CSS2 не определяет, когда пользовательский агент может разместить такой элемент рядом с float-ом или насколько этот элемент может стать у́же.

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

4. Изучено? Предсказуемо?


После такого сюрприза (из самой спецификации!), у меня появилось подозрение, что он у float-ов не единственный. Так и вышло. В обсуждении статьи о перспективных механизмах CSS-раскладки на сайте web-standards.ru я случайно натолкнулся на чудесный пример:
<div style="float:left">
  <div style="float:left">X</div>
  <div>Y</div>
</div>

Вроде бы всё просто: один плавающий блок в другом. Как вы думаете, как это отобразится в браузерах (включая ваш)? И кто из браузеров прав? (Пусть название файла по ссылке вас не сбивает — есть серьезные соображения, что баг как раз в других браузерах). Официальный набор тестов CSS 2.1, насколько я могу судить, эту ситуацию «старательно» обходит (если я неправ — прошу ткнуть ссылкой).

Добавлено 25.01.2012: в ходе небольшого обсуждения выработался консенсус, что правильно пример отображают новые IE и Оперы, а поведение Gecko и Webkit-а — увы, баг (в Багзилле он даже давно заведен). Большая просьба ко всем неравнодушным к будущему веба проголосовать за повышение приоритета этого бага!

5. Какая же отсюда мораль?


Из объективных свойств реальности трудно делать практические выводы, но по личной просьбе кэролловской Герцогини — черкну пару-тройку:
  • Несмотря на давнюю историю поддержки и использования, «белых пятен» и «подводных камней» в свойствах float и clear пока, увы, хватает.
  • Любые инструменты желательно использовать по назначению. Как ни крути, float-ы не предназначены для раскладки колонок и т.п., их исконное назначение — небольшие врезки (иллюстрации, пояснения и т.п.), и с этой задачей они справляются «на ура».
  • Тем не менее, набора инструментов для укрощения float-ов — clear для блоков и целой россыпи свойств, создающих в блоке новый контекст форматирования, включая сам float — достаточно для основной массы практических задач (у каждого способа свои плюсы и минусы, но хотя бы есть из чего выбирать). Это облегчается тем, что браузеры во многом ведут себя единообразно, хотя формально по спецификации имеют право вести себя иначе.
  • И всё-таки с float'ами нужна осторожность («забавы на воде к добру не приводят»).
  • И главное: старайтесь не «плавать» в спецификации — чтобы в любом, самом сложном потоке не разделить судьбу кораблика со второй картинки топика:). Знание спецификации — сила! Хотя и, как выясняется, не всесильная.


Спасибо, что осилили! Буду рад уточнениям, дополнениям, разгромной критике и прочим помидо откликам в комментах.



1 Самый удачный вариант перевода для «inline», который мне встретился. Вариант «встроенный» сбивает с толку, «строчный» приводит к путанице между «inline box» и «line box», калька «инлайновый» не добавляет ясности. А так сразу понятно, что это бокс внутри строки. Используется, например, в переводе CSS2 В. Курашева.
+82
20 января 2012, 21:09
487

комментарии (44)

+1
marcus #
Пример в разделе 4 замечательно иллюстрирует, что следование стандартам — не панацея и не единственный показатель.
+2
SelenIT2 #
А также важность как можно более полного покрытия спецификации тестами. К сожалению, у CSS 2.1, несмотря на принятие финального статуса, с этим далеко не всё ладно (есть еще более вопиющий пример, который все браузеры рендерят не по спеке — а в тестах его как нарочно «забыли»)…
0
arinoki #
Здорово описали, плюс доходчивые примеры. Я сам пока в вёрстке не сильно шарю, пока хватает метода «тыка», htmlbook.ru и css_reset.css, но такие статьи весьма полезны, спасибо. И тестирование в 4 браузерах, конечно же.
0
SelenIT2 #
Боюсь, что приходится во всех 5). Хром и Сафари в последнее время, по моим впечатлениям, ощутимо разошлись и спотыкаются на совсем разных местах.
0
arinoki #
ie 9, fox 9, chrome 19, opera 12. Как-то так. Сужу по своему блогу — посетителей с safari крайне мало.
+3
JeanLouis #
Отличная статья, добьем это свойство наконец. Поставил ссылку на эту статью в своем исходном топике.
0
egorinsk #
Статья плохая, так как нет цветных иллюстраций со стрелочками и квадратиками, а представлять мысленно то, что тут описано, сложно, а самому рисовать лень.

Единственная здравая мысль — то, что в первую очередь надо читать стандарт.
+5
sapp #
А по-моему наоборот, я приятно удивился количеству живых примеров, которые автор позаботился привести.
+1
SelenIT2 #
Есть пара ссылок на интерактивные примеры (правда, внутри текста). Критику насчет иллюстраций принимаю, иллюстрации, как смогу, добавлю.
0
SelenIT2 #
Добавил цветную иллюстрацию со стрелочками и квадратиками (см. обновленный пост). Стало лучше?
0
egorinsk #
Что-то кернинг не очень. А в блоке с float: right лежит что-то съедобное?
0
SelenIT2 #
К сожалению, несъедобное — всего лишь рыбка (символ естественности плавания) из «тотемного» материала автора (селенита:).

Насчет кернинга — уж извините, использованный движок его тупо не поддерживал. Fed quod potui
0
zokotuhaFly #
По-моему показывать примеры вёрстки на реальных предметах — это отличная идея, не бросайте этот приём! Теперь они — незыблемая часть статьи.
0
sapp #
Давно не осиливал такие длинные статьи. Полезно и интересно! Спасибо! :)
0
kokis #
Наконец-то я полностью уловил суть этого свойства. Хотя, ему, в плане верстки страниц, уже давно пора в отставку.
0
jack128 #
а что в замен?? ??
+1
kokis #
+1
jack128 #
аааа. Я то думал, что то что уже сейчас можно использовать, существует…
+1
AntonDiaz #
К сожалению, еще очень нескоро мы сможем всем этим пользоваться без опасений по поводу совместимости.
0
SelenIT2 #
Кстати, похоже, у флексибоксов будущее достаточно светлое. Если не гнаться за попиксельной точностью в «динозаврах», кое-где уже через годик можно будет начинать использовать подход а-ля «mobile first» (старью и Операм-мини — минималистичный текстовый вариант, взрослым — гламурненькие флексибоксики).

Вот до тимплейт-лейаутов, боюсь, прогресс уже едва ли дойдет. А жалко, многообещающая была задумка...
0
SelenIT2 #
Хотя вот здесь пишут, что эти суровые зеленые ребята из W3C опять поменяли синтаксис в спеке — как раз в момент, когда предыдущий вариант худо-бедно был реализован аж в трех движках. Да что ж за ерунда творится… =)
+2
AntonDiaz #
Grid Layout забыли. На Хабре, кстати, описывали даже.
0
IkaR49 #
Вот жил я со своим представлением о работе float: left. Жил, никому не мешал… А тут пришли вы…

P.S. Статья понравилась. Понятна и доходчива, спасибо :)
0
zayko #
Обстоятельно. Увлекательно. Спасибо.
+1
Gaen #
Спасибо, я давно пытался понять непостижимые тайны поведения плавающих блоков! Вы вдохновили меня на вдумчивое чтение спецификации.
0
rodweb #
Я уже привык колонки с помощью float и clear делать.
Кстати, вот вопрос: если в контейнере шириной, например, 500px, размещать один за другим в строку 5 дивов шириной 100px, то пятый див неизбежно сползает вниз, хотя по длине должен тютелька в тютельку войти. Почему так происходит и как с этим жить?
0
rodweb #
Забыл самое главное, у дивов display: inline-block;
+1
SelenIT2 #
Почти наверняка дело в пробелах между тегами. Сами инлайн-блоки живут во внутристрочном (инлайновом) контексте форматирования, поэтому пробелы между ними значимы и отображаются как обычные пробелы между словами в тексте. Есть куча способов худо-бедно нейтрализовать это силами CSS (обнуление font-size контейнера с выставлением его самим блокам заново, отрицательные margin-ы, отрицательный word-spacing и т.д.). Но самое надежное, универсальное и честное решение — просто убрать пробельные символы (включая переносы строк) между соседними тегами.
+1
rodweb #
Я обычно решаю эту проблему установкой white-space: nowrap; для контейнера. Странно, что переносы срок в коде превращаются в пробелы между элементами.
0
SelenIT2 #
Особой странности не вижу, это вариант, разрешенный спецификацией (раздел 16.6.1. п.3).
0
rodweb #
А есть ли возможность «подавить» влияние переносов строк на появление пробелов между инлайновыми элементами? Если склеивать код элементов в одну длинную строку, он становится менее читабельным :)
+2
SelenIT2 #
Есть несколько возможностей «скукожить» эти пробелы визуально до нуля (см. выше), но у каждого из них свои минусы и ограничения.

Весь код в одну длинную строку склеивать незачем, достаточно убрать пробел между закрывающим тегом одного инлайн-блока и открывающим следующего:
<div class="container">
    <div class="inline-block">
        <!-- код первого блока - как обычно -->
        ...
    </div><div class="inline-block">
         <!-- код второго блока - как обычно -->
        ...
        ...
    </div><div class="inline-block">
         <!-- код последнего блока - как обычно -->
        ...
    </div>
</div>

Еще есть вариант с оборачиванием самих этих переносов в HTML-комменты, но это уже совсем от беды). А если эти блоки выводятся шаблонизатором, можно настроить его, чтоб выводил подряд, без пробелов.
0
rodweb #
Спасибо, как я сам не додумался :)
0
fatal #
Можно ещё играться с отрицательным word-spacing, но там свои глюки и проблеммы в отдельных случаях… Везде подводные рифы к сожалению, да.
+2
SelenIT2 #
Варианты с отрицательным чем-то упираются в зависимость от конкретного шрифта — пробел может варьировать от 0,2 до 0,5 em с лишком, грубо. Плюс именно word-spacing для инлайн-блоков упирается в баг вебкитов (обходится заданием display: table для контейнера, тогда пробелы сами пропадают — но это другой баг, поэтому вряд ли так будет всегда:). Я пришел к выводу, что единственное решение — честно убрать пробелы, остальное — имитация.

Для списков, кстати, для сохранения читабельности можно воспользоваться непопулярной фичей HTML (в т.ч. HTML5) — опустить опциональные закрывающие </li> :). Неявное закрытие происходит сразу перед открывающим тегом следующего блока, а значит, никаких пробелов не будет.
+1
magic4x #
В Django templates есть замечательный аспирин в виде тега {% spaceless %}{% endspaceless %}. Заворачиваете в него инлайн-блоки (можно вместе с контейнером) и наступает чудо — он удаляет пробелы между тегами. Это на всякий случай, авось будете работать с джангой.
0
DenisZ #
Обнуление font-size у родительского блока, мне кажется, самый удобный вариант. Правда, нужно не забывать выставить font-size для самого элемента.
0
SelenIT2 #
У него большой плюс — независимость от шрифта. Но и ощутимый минус — невозможность сквозного наследования шрифта от задания его в одном месте (напр. для body), что могло бы сильно упростить, например, превращение дизайна в адаптивный. Плюс у вебкитов тоже бывают странности, по крайней мере раньше при нулевом font-size они всё равно оставляли однопиксельные зазоры. Так что и это не панацея.
+1
fatal #
О, мой тест в статье :) Рад, что если не браузеру Опера, то хоть кому-то польза.
0
SelenIT2 #
Да, меня впечатлило :). Но вы уверены, что ошибаются именно новые Оперы и IE, а правильное отображение — в две строки? У меня сложилось впечатление, что духу спецификации больше отвечает отображение в одну…
0
fatal #
Если бы я был уверен, то я бы уже написал баг-репорт в Оперу. А так — нет.
Спасибо, что нашли что-то похожее в мозиловском баг-трекере. Посмотрим, что скажут. Моё предсказание, что ничего — слишком все озабочены бирюльками CSS3, хоть и тоже нужными.
0
SelenIT2 #
Вроде там же нашлось еще более прямое попадание. Сам Дэвид Бэрон, судя по всему, подтверждает наличие бага в FF и правоту IE и Опер. Объединим усилия и поднимем багу приоритет?
+1
Ekstazi #
Хорошая статья, спасибо. Хотелось бы еще посоветовать сайт htmlbook.ru изучив информацию которого я и понял как работает float.
+1
positivecrash #
«Что еще за clear, что и от чего он очищает?» поразительно. это тянет на захватывающую диссертацию

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.