Float'омагия: пробуем не «плавать» в спецификации, чтобы не утонуть в потоке
Недавняя статья про float-ы и реакция хабрасообщества на нее показали, что «сладкая парочка» CSS-свойств float и clear, несмотря на свою давнюю и колоссальную популярность в верстке, для многих остается чем-то загадочным и таинственным. Однако затронута в ней оказалась лишь верхушка айсберга, теряющегося в пучине мутных вод загадочной спецификации CSS 2.1. А есть и «подводные рифы», в спецификации не описанные. Не желаете ли продолжить увлекательное погружение в этот мир удивительных открытий и добраться до самого дна, полного сокровищ? Если да, то… 1. Немного азбучных основ: с чем и как взаимодействуют float-ы
Все знают, что блоки с
float «вырываются/выпадают из нормального потока», но мало кто может толком сказать, что же это за «нормальный поток». Даже спецификация. В самом начале раздела 9.3 первый пункт гласит:В CSS 2.1 нормальный поток включает блочное форматирование блочных боксов, внутристрочное форматирование внутристрочных1 боксов и относительное позиционирование тех и других.
Дальше упоминаются понятия блочного и внутристрочного контекстов форматирования. Четких определений этих контекстов мне найти так и не удалось (еще одна тайна?), зато сказано, что элемент может принимать участие только в одном из этих контекстов.Со стороны всё как будто несложно. Бокс, грубо — это прямоугольник. Прямоугольники бывают двух типов: большие, выстраиваемые друг за дружкой по вертикали (сверху вниз), как штабель из ящиков — это блочные боксы (блоки). И маленькие, выстравиваемые друг за дружкой цепочкой по горизонтали, слева направо (или справа налево), ряд за рядом, как кладка из кирпичей — это внутристрочные боксы («инлайны»). В первом случае мы наблюдаем блочный контекст форматирования (где участвуют сами блоки), а во втором — внутристрочный (разбиение текста на строки внутри блока).
С добавлением float-ов начинаются чудеса. Начиная с определения:
В плавающей модели бокс сначала размещается согласно нормальному потоку, затем убирается из потока и смещается вправо или влево на максимально возможное расстояние. Контент может обтекать float вдоль одной стороны.Чувствуете парадокс: бокс из потока вынут — но на поток контента, тем не менее, влияет!
Так вот: оказывается, 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-а.
Аналогия с дорожным просветом автомобиля, по-моему, вполне уместна. Без клиренса выступающие из вышележащих блоков 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 В. Курашева.



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