Через день после написания статьи про поток я вдруг обнаружил, что совершенно бессовестным образом позабыл написать там о том, как он работает с точки зрения именно раскладки. Забыл, то есть, самую суть :-).
Исправляю это упущение!
Даем место
Очень часто при раскладке страницы возникает задача вынуть что-то из общего потока страницы и повесить рядом с основным содержимым и оформить в виде плавающей панельки.
В этом случае можно тот блок, который будет панелью, повесить абсолютно, а основное содержимое оставить в потоке. Однако этого недостаточно, потому что панелька залезет сверху текста.
Вот здесь и начинают работать границы и отступы. Их основная задача: сдвинуть край блока в потоке, чтобы дать место для чего-то другого. Задаются они свойствами margin-left
, margin-right
, padding-left
, padding-right
.
Часто в разных форумах наталкиваюсь на "извечные вопросы", один из которых: что использовать - margin или padding. Причем, "экспертные" ответы бывают часто в духе "надо использовать всегда только margin (padding), потому что padding (margin) глючит". Это, конечно, неверно. Глючить та или иная раскладка может по миллиону причин, и выбор отступов или границ тут редко реально виноват.
Суть выбора же предельно проста: padding находится внутри границ, и на нем нарисован фон бокса, а margin - за границами, и он прозрачен. Поэтому выбирать надо исходя из того, как вы хотите, чтобы оставляемое пустое место выглядело: внутри блока или снаружи. Если же у вас нет ни фона, ни границ, то разницы никакой и нет. Хоть монетку подкидывайте.
Схлопывание границ
По этому поводу есть очень хорошая и ставшая уже классической статья гуру CSS Эрика Мейера. Эта глава, по большей части - вольное изложение ее по-русски.
Схлопывание границ ("margin collapsing") - это на первый взгляд странный эффект, который наблюдается с верхней и нижней границами блоков. Проявляется он двояко.
Наложение границ соседей
Первое проявление - это когда границы между двумя соседними блоками "проваливаются" друг в друга. Для примера возьмем два блока - заголовок и абзац, идущие в потоке один за другим:
<h1>Заголовок</h1>
<p>Абзац текста</p>
Дадим заголовку верхнюю и нижнюю границу в 20 пикселов, а абзацу - в 10.
h1 {
margin:20px 0;
}
p {
margin:10px 0;
}
Кстати, вот эти "10px 0
" - это удобная сокращенная запись. Первая цифра задает верх и низ, вторая - право и лево. Она подходит не только для margin'ов, но и для всех остальных свойств, которые могут задаваться для сторон бокса.
Так вот, нижняя граница заголовка и верхняя граница абзаца наложатся друг на друга и между ними будет не 30 пикселов, а 20 (по наибольшей границе).
Вываливание за родительские границы
Эта штука менее очевидна. Представьте себе несколько абзацев, идущих один за другим, заключенных в один общий блок-контейнер:
<div>
<p>абзац</p>
<p>абзац</p>
<p>абзац</p>
</div>
Дадим и контейнеру и абзацам верхние и нижние границы:
p {
margin:10px 0;
}
div {
margin:5px 0;
}
Здесь схлопывание проявляется в том, что верхний margin первого абзаца не лежит внутри div'а, а вываливается из него наверх.
Margin самого контейнера и margin первого блока накладываются, и в итоге место перед контейнером равно наибольшему из них.
Тоже самое происходит и внизу с нижними margin'ами контейнера и последнего вложенного блока.
Отчего так
Схлопывание границ - не глюк и не ошибка браузеров. Это специально придуманное поведение. Оно, хоть и кажется на первый взгляд странным, совершенно нормально для того, для чего поток служит в первую очередь: форматирования текста.
В частности, наложение границ соседних элементов очень полезно, когда вы определяете границы для заголовков, абзацев, списков - основных блочных элементов текста. Без схлопывания в группе абзацев расстояние между ними было бы больше, чем перед первым и после последнего.
Это можно было бы обойти заданием только нижней или только верхней границы, но тогда пришлось бы отдельно бороться с тем, что первый или последний абзац прилипают к границам своего контейнера. Со схлопыванием же вы всегда можете задавать элементу те границы, которые нужны именно ему, не думая о том, что его окружает.
Вываливание границы за пределы родителя тоже имеет свой смысл. В первую очередь - для списков. Представим себе текст, в котором есть список. Элемент списка (<li>
) содержит абзац (<p>
). И у абзаца, и у элемента списка есть свои границы.
Так вот, если бы верхняя граница абзаца не вываливалась из элемента списка наверх, то текст абзаца съезжал бы вниз, и не был бы напротив точечки списка. А это некрасиво.
Проблемы при раскладке
C простым потоком текста схлопывание работает хорошо, а вот при раскладке иногда оно аукается совсем не так, как хотелось бы. Возьмем простой пример. Нам нужен в начале страницы верхний блок, в котором будут заголовок и подзаголовок:
<body>
<div id="header">
<h1>Суперконтора</h1>
<p><small>Суперконторим с 1381 года</small></p>
</div>
</body>
Дальше по совету гипотетического мегадизайнера нашей суперконторы мы хотим, чтобы весь верхний блок прилегал к краям страницы, имел темно-красный фон, заголовок "Суперконтора" имел бы ярко-красный фон, а текст внутри всего верхнего блока был белым. И кроме того, хочется, чтобы вокруг текста были определенные отступы. Тогда гиперверстальщик нашей суперконторы пишет такой стиль:
body {
margin:0;
}
#header {
background-color:darkred; color:white;
}
#header h1 {
margin:20px 0 10px 0;
background-color:red;
}
#header p {
margin:5px 0;
}
И получается у нас вот такое:
Белое пространство над верхним блоком - это вывалившийся из него верхний margin заголовка! Явно не то, чего нам хотелось. Нам хотелось, чтобы этот margin сидел внутри верхнего блока, и чтобы вместо дешевенького белого фона там был трендовый темно-красный! Хотя с точки зрения чистого текст все нормально - 20 пикселов свободного места сверху заголовка присутствуют. Но теперь, поскольку у нас есть фон, нам далеко не все равно, сидит этот margin внутри верхнего блока или вываливается наружу.
Существует несколько способов, отменяющих вываливание границ элементов за пределы контейнера.
-
Вываливание отменяется, если блоку-контейнеру назначить padding или border (с той стороны, где вываливается). В нашем примере это будет выглядеть так:
#header { background-color:darkred; color:white; padding:1px 0; }
Минус состоит в том, что высота всего верхнего блока увеличивается на 2 пиксела. Это может и не быть чем-то страшным, но если на высоту этого блока ориентируются другие элементы дизайна, то это не очень хорошо. И нет, нулевой padding не заработает :-). Это все равно, что его нет. 2. В случае, когда пикселы все же лишние, есть другой способ. Можно вываливающуюся верхнюю границу у заголовка убрать, а вместо нее поставить верхний padding самому контейнеру. Визуально эффект будет как раз нужный. И то же самое можно проделать и снизу блока.
#header { padding-top:20px; padding-bottom:5px; } #header h1 { margin:0 0 10px 0; } #header p { margin:5px 0 0 0; }
Сокращенная запись из четырех чисел расставляет значения, начиная с верхнего по часовой стрелке (верх, право, низ, лево). То есть, запись
0 0 10px 0
означает "все границы - 0, нижняя - 10 пикселов".Правда, в этом случае CSS уже получается слегка более дремучим, что затрудняет понимание написанного через недельку-другую. 3. Еще один способ отключить вываливание строится на том, что оно, как я говорил, придумано для форматирования текстовых блоков в потоке. А для блоков, исключенных из потока, вываливание отменяется. Ну а чтобы изъять блок из потока, его можно, например, спозционировать абсолютно. Забегая вперед скажу, что есть еще один способ - свойство
float
, но о нем будет целая следующая статья.Однако надо сказать, что позиционирование - это все же не способ "исправления" ситуации. Потому что если в странице взять, да и изъять из потока какой-то блок, то скорее всего все вокруг сломается :-). Поэтому этот способ можно переформулировать: если вы все равно собирались блок позиционировать абсолютно, то о проблеме вываливания границ можно не заботиться.
Итог
Итак, вываливание границ за пределы контейнера может вызывать некрасивые эффекты. Для борьбы с ним есть три способа со своими плюсами и минусами.
Способ | Плюсы | Минусы |
---|---|---|
Отменяющие однопиксельные padding или border | Очень простой способ | Лишние пикселы в дизайне зачастую недопустимы |
Полная замена крайних margin'ом вложенных блоков padding'ами контейнера | Полная визуальная идентичность желаемому | Задание отуступов "размазывается" по разным правилам в стилях, что их неприятно усложняет |
Абсолютное позиционирование или float | Полная визуальная идентичность и простота | Блок вынимается из потока и меняет раскладку |
Эта статья - часть находящегося в процессе написания цикла под рабочим названием "Учебник". Я рекомендую ознакомиться и с другими статьями, которые можно найти в категории "Учебник", где они сейчас собраны в обратном хронологическом порядке.
Комментарии: 20
С тем, про что написано, всё понятно, а вот почему в IE появляется отступ на 1-2 пикселя от края плавающего блока? Вот пример (как раз этим сейчас занимаюсь) - http://ph-web.com.ru/shop2/example.php?group_id=4&id=1
Не совсем понял, как это связано с отступами в потоке :-). Скорее всего, это один из многих багов IE. Подозреваю, что этот: http://www.positioniseverything.net/explorer/threepxtest.html
[…]Мне лично более импонирует в этом вопросе позиция Ивана Сагалаева, который точно подметил, что с появлением IE 7 старые версии IE ни куда не денутся… Конечно же это личное дело каждого, но… не нужно сбрасывать со счетов IE 5.x и IE 6. […]
Совершенно согласен с Иваном Сагалаевым :-)
Старые версии IE никуда не денутся из-за трудности с получеием новых (легально, конечно) - за них надо платить дополнительные деньги. (Хм, за исправление недоработок). А вот на старые версии Opera или FireFox, как мне кажется, можно необращать внимания, т.к. новые исправленные всегда можно получить почти бесплатно (опять же, полне легально).
К сожалению, не знаю как там обстоят дела с остальными браузерами.
Спасибо за еще одну замечательную статью.
Все описанные методы борьбы со схлопыванием при раскладке работают "как часы". ;-)
Не совсем только понятно почему IE6 не подчиняется правилам "вываливания за родительские границы" даже в стандартном режиме рендеринга...
Опечаточка в текст закралась:
За статьи спасибо, CSS основательно занимаюсь уже года 3, но некоторых упомянутых решений (в смысле, work-around которые по-английски) не знал.
http://www.w3.org/TR/CSS2/box.html#collapsing-margins
Отступы также не схлопываются у абсолютно и относительно отпозиционированных блоков. Правда, в css2.1 оставили только абсолютно отпозиционированные (непонятно, зачем), но на поддержке браузерами это не сказалось.
Чтобы блок не вываливался за верхнюю (нижнюю)границу контейнера, можно для контейнера прописать определение display:table
а так же overflow:hidden
Этим способам есть простое объяснение: поля (margin) схлопываются при соприкосании. При добавлении padding и border соприкосания не происходит, hidden отсекает поля друг от друга.
Не обессудьте, если не в тему.. "Создал" http://www.vamatveychuk.narod.ru используя исключительно абсолютное позиционирование, но коректно отображается страница только в IE. Как исравить? Заранее благодарен за помощь..
В качестве подсказки:
и так далее и тому подобное — позиционируем блоки относительно друг друга с использованием css.
Подскажите пожалуйста, почему отступ вложенного блока влияет на отступ родительского(если он пустой) и как от этого избавиться? Пример:
CSS:
Спасибо за статью.
Только что в очередной раз столкнулся с этой «проблемой», и, хоть решения я уже знал, хотелось выяснить, в чём, собственно, дело.
Большое спасибо за статью и разложение всего по полочкам! Также, спасибо Webmolot'у за комментарии, попробовала Ваши способы - всё работает!
было интересно узнать откуда растут ноги у таких завалов, это же явно было не баги. В своем опыте такие вещи не применяю никогда, гораздо понятнее сделать все через float'ы.
Все три способа плохи. Про первый и говорить не буду, и так всё ясно.
Про второй. Это плохое решение, если у вас много заголовков и параграфов в родительском блоке. В этом случае придется отдельно убирать отступы для первых и последних абзацев и параграфов. Очень неудобно.
Про третий. Не подходит, если нужно сохранить автоширину, заполняющая всю ширину родителя. Ведь флоаты и абсолютно позиционированные элементы имеют ширину контента. Width:100% не покатит, если у нашего заголовка есть свои padding-и.
После некоторых размышлений придумал другой способ. Нужно просто добавить псевдоэлементы нулевого размера в родитель (на манер clearfix) со свойством display:table. В HTML код надо просто добавить класс "nocollapse" к тому родительскому элементу, у которого возникает схлопывание с дочерними. А в CSS следующий код:
В случае со старыми IE, которые не поддерживают display:table (до 7 включительно), возможно, поможет zoom:1, хотя я не тестировал.
Ах да, принцип как раз такой же получился, как и в новом clearfix.
Вот как выглядит новый вариант clearfix из html5boilerplate (борется как с вываливающимися флоатами, так и вываливанием margin-ов из родителя):
А почему в новом clearfix используется именно display:table ? Можно же display:block. Результат тот же, проще для понимания, и хак для осклика не нужен.
Может быть это обьяснение(нашел в одной из либ)
// 2. The use of
table
rather thanblock
is only necessary if using //:before
to contain the top-margins of child elements.