Через день после написания статьи про поток я вдруг обнаружил, что совершенно бессовестным образом позабыл написать там о том, как он работает с точки зрения именно раскладки. Забыл, то есть, самую суть :-).

Исправляю это упущение!

Даем место

Очень часто при раскладке страницы возникает задача вынуть что-то из общего потока страницы и повесить рядом с основным содержимым и оформить в виде плавающей панельки.

В этом случае можно тот блок, который будет панелью, повесить абсолютно, а основное содержимое оставить в потоке. Однако этого недостаточно, потому что панелька залезет сверху текста.

Вот здесь и начинают работать границы и отступы. Их основная задача: сдвинуть край блока в потоке, чтобы дать место для чего-то другого. Задаются они свойствами 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;
}

И получается у нас вот такое:

Верхние 20 пикселов над заголовком находятся не внутри блока на темно-красном фоне, а снаружи - на белом фоне body.

Белое пространство над верхним блоком - это вывалившийся из него верхний margin заголовка! Явно не то, чего нам хотелось. Нам хотелось, чтобы этот margin сидел внутри верхнего блока, и чтобы вместо дешевенького белого фона там был трендовый темно-красный! Хотя с точки зрения чистого текст все нормально - 20 пикселов свободного места сверху заголовка присутствуют. Но теперь, поскольку у нас есть фон, нам далеко не все равно, сидит этот margin внутри верхнего блока или вываливается наружу.

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

  1. Вываливание отменяется, если блоку-контейнеру назначить 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

  1. d[D]E

    С тем, про что написано, всё понятно, а вот почему в IE появляется отступ на 1-2 пикселя от края плавающего блока? Вот пример (как раз этим сейчас занимаюсь) - http://ph-web.com.ru/shop2/example.php?group_id=4&id=1

  2. Иван Сагалаев

    Не совсем понял, как это связано с отступами в потоке :-). Скорее всего, это один из многих багов IE. Подозреваю, что этот: http://www.positioniseverything.net/explorer/threepxtest.html

  3. [...] В блогосфере на этой неделе гулял устойчивый ветерок хайтека в области веб-технологий и блоггинга. К примеру сразу на нескольких блогах вспыхнула полемика около знаменитого звездочного CSS-хака для IE. Никита Вакорин на мой взгляд “поспешил” с заявлениями на эту новость и рекомендациями убирать этот хак из своих CSS-файлов. Мне лично более импонирует в этом вопросе позиция Ивана Сагалаева, который точно подметил, что с появлением IE 7 старые версии IE ни куда не денутся… Конечно же это личное дело каждого, но… не нужно сбрасывать со счетов IE 5.x и IE 6. Ну и коли уж мы коснулись CSS, то не проходите мимо другой статьи Ивана – “Границы и отступы в потоке“, в которой подробно изложено как работать с margin- и padding-свойствами блоков. [...]

  4. Ireruss

    […]Мне лично более импонирует в этом вопросе позиция Ивана Сагалаева, который точно подметил, что с появлением IE 7 старые версии IE ни куда не денутся… Конечно же это личное дело каждого, но… не нужно сбрасывать со счетов IE 5.x и IE 6. […]
    Совершенно согласен с Иваном Сагалаевым :-)
    Старые версии IE никуда не денутся из-за трудности с получеием новых (легально, конечно) - за них надо платить дополнительные деньги. (Хм, за исправление недоработок). А вот на старые версии Opera или FireFox, как мне кажется, можно необращать внимания, т.к. новые исправленные всегда можно получить почти бесплатно (опять же, полне легально).
    К сожалению, не знаю как там обстоят дела с остальными браузерами.

  5. Константин

    Спасибо за еще одну замечательную статью.
    Все описанные методы борьбы со схлопыванием при раскладке работают "как часы". ;-)
    Не совсем только понятно почему IE6 не подчиняется правилам "вываливания за родительские границы" даже в стандартном режиме рендеринга...

  6. Yuri

    Опечаточка в текст закралась:

    но о нем будет целая следующая статься.

    За статьи спасибо, CSS основательно занимаюсь уже года 3, но некоторых упомянутых решений (в смысле, work-around которые по-английски) не знал.

  7. RommeDeSerieux

    http://www.w3.org/TR/CSS2/box.html#collapsing-margins

    Отступы также не схлопываются у абсолютно и относительно отпозиционированных блоков. Правда, в css2.1 оставили только абсолютно отпозиционированные (непонятно, зачем), но на поддержке браузерами это не сказалось.

  8. Глеб

    За статьи спасибо, CSS основательно занимаюсь уже года 3, но некоторых упомянутых решений (в смысле, work-around которые по-английски) не знал.>
    Согласен полностью, только счас увидел про это.
    Спасибо огромное за статью

  9. webmolot.com

    Чтобы блок не вываливался за верхнюю (нижнюю)границу контейнера, можно для контейнера прописать определение display:table

  10. webmolot.com

    а так же overflow:hidden
    Этим способам есть простое объяснение: поля (margin) схлопываются при соприкосании. При добавлении padding и border соприкосания не происходит, hidden отсекает поля друг от друга.

  11. Delirium

    Не обессудьте, если не в тему.. "Создал" http://www.vamatveychuk.narod.ru используя исключительно абсолютное позиционирование, но коректно отображается страница только в IE. Как исравить? Заранее благодарен за помощь..

  12. Fil

    В качестве подсказки:

    <style type="text/css">
      #top_car { float: left; }
    </style>
    

    и так далее и тому подобное — позиционируем блоки относительно друг друга с использованием css.

  13. John

    Подскажите пожалуйста, почему отступ вложенного блока влияет на отступ родительского(если он пустой) и как от этого избавиться? Пример:

    <body>
    <div id="b1">
      <div id="b2">
      </div>
    </div>
    </body>
    

    CSS:

    body {
    margin: 0;
    padding: 0;
    }
    #b1 {
    height: 200px;
    background: #aaa;
    }
    #b2 {
    margin-top: 20px;
    height: 100px;
    background: #aa1;
    }
    
  14. www.google.com/profiles/andrey.g.matveev

    Спасибо за статью.

    Только что в очередной раз столкнулся с этой «проблемой», и, хоть решения я уже знал, хотелось выяснить, в чём, собственно, дело.

  15. Алёна

    Большое спасибо за статью и разложение всего по полочкам! Также, спасибо Webmolot'у за комментарии, попробовала Ваши способы - всё работает!

  16. Никита Лебедев

    было интересно узнать откуда растут ноги у таких завалов, это же явно было не баги. В своем опыте такие вещи не применяю никогда, гораздо понятнее сделать все через float'ы.

  17. Anton Diaz

    Все три способа плохи. Про первый и говорить не буду, и так всё ясно.

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

    Про третий. Не подходит, если нужно сохранить автоширину, заполняющая всю ширину родителя. Ведь флоаты и абсолютно позиционированные элементы имеют ширину контента. Width:100% не покатит, если у нашего заголовка есть свои padding-и.

    После некоторых размышлений придумал другой способ. Нужно просто добавить псевдоэлементы нулевого размера в родитель (на манер clearfix) со свойством display:table. В HTML код надо просто добавить класс "nocollapse" к тому родительскому элементу, у которого возникает схлопывание с дочерними. А в CSS следующий код:

    .nocollapse:before, .nocollapse:after {
        width: 0;
        height: 0;
        display: table;
        content: " ";
    }
    

    В случае со старыми IE, которые не поддерживают display:table (до 7 включительно), возможно, поможет zoom:1, хотя я не тестировал.

  18. Anton Diaz

    Ах да, принцип как раз такой же получился, как и в новом clearfix.

    Вот как выглядит новый вариант clearfix из html5boilerplate (борется как с вываливающимися флоатами, так и вываливанием margin-ов из родителя):

    .clearfix:before,
    .clearfix:after {
        content: " ";
        display: table;
        }
    
    .clearfix:after {
        clear: both;
        }
    
    .clearfix {
        *zoom: 1; /* Для IE6, IE7 */
        }
    
  19. Рауль

    А почему в новом clearfix используется именно display:table ? Можно же display:block. Результат тот же, проще для понимания, и хак для осклика не нужен.

  20. Николай

    Рауль

    Может быть это обьяснение(нашел в одной из либ)

    // 2. The use of table rather than block is only necessary if using // :before to contain the top-margins of child elements.

Добавить комментарий