Победа над неочевидным. Схлопывание внешних отступов

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

Наверное, каждый сталкивался со свойством border-collapse для таблиц. Известно, что данное свойство со значением border-collapse:collapse удаляет одну из границ для соседних ячеек таблицы, предотвращая дублирование их границ. Схоже работает и особенность блочной модели, называемая схлопыванием внешних отступов (англ. — Collapsing Margin). Схлопывание внешних отступов — особенность блочной модели CSS, которая заключается в наложении вертикальных внешних отступов двух или более блочных элементов (которые могут быть или не быть соседями) для формирования одиночного внешнего отступа. Отступ, сформировавшийся в результате данного объединения, называется схлопнувшимся отступом. Заметьте, что данная особенность применима только к вертикальным отступам элемента, т.е. к margin-top и margin-bottom.

Для упрощения, представляйте схлопывание следующим образом: когда внешние вертикальные отступы блочных элементов соприкасаются, то остается отступ, значение которого равняется значению большего из них, при этом значение отступа элемента с меньшей границей равно нулю. На рисунке ниже размер одной клетки фонового рисунка равен 20 пикселей; как видим, отступ между элементами равен 40 пикселей, хотя на первый взгляд, глядя на код, человеку, не знакомому с принципами схлопывания, может показаться, что блоки размещены неправильно, так как их отступы не плюсуются:



#block1 {
   margin:40px;  /*Задаем для первого блока отступы со всех сторон 40 пикселей */
}

#block2 {
    margin:40px; /*Задаем для второго блока отступы аналогично первому */
    margin-top:20px; /* Переопределяем верхний отступ */
}

*Исходный код примера

В случае, если одна с величин имеет минусовое значение, то финальное значение схлопнувшегося отступа формируется путем суммирования исходных значений блочных элементов. В случае, если обе величины отрицательные, то используется большее отрицательное значение (меньшее с арифметической точки зрения). Например, если бы block1 имел значение margin-bottom:-60px, а блок block2 margin-top:50px, то финальное значение отступа между элементами было бы минус 10 пикселей.

Как и в любой другой концепции, здесь тоже бывают исключения, поэтому схлопывание не работает в следующих случаях:
  • с «плавающими» блоками, которые используют свойство float;
  • с корневыми элементами(html, body);
  • с абсолютно позиционируемыми элементами, имеющих свойство и значение position:absolute;
  • в строчных элементах.

Существуют также специфические исключения, применяемые в зависимости от того, в каком иерархическом взаимодействии находятся блоки (об этом ниже):
  • Если элементы имеют значение свойства overflow, отличимое от visible, то в таких элементах не схлопываются отступы с отступами их наследников.
  • Элементы со свойством cleared не схлопываются верхними отступами с нижними отступами их родителей.

Теория может быть сложна для понимания, поэтому перейдем к примерам. Рассмотрим классические случаи, существующие в наследовании: когда блоки имеют связь потомок-потомок (sibling-sibling), элементы соседствуют друг с другом и имеют общего родителя; а также когда блоки имеют связь родитель-потомок (parent-child) — один элемент вложенный в другой. В обоих случаях схлопывание успешно применяется, но имеет несколько нюансов, о которых стоит поговорить.

Соседние элементы и схлопывание


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

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



#parent {
    margin: 40px;
    border:1px dashed white;
    background:rgba(150, 0, 255, 0.2);
    width:204px;
}  /* Для визуальной симметрии потребовалось бы добавить padding-bottom:20px; */

.block {
    height:16px;
    margin-top: 20px;
    border:2px solid white;
    background:rgba(150, 0, 255, 0.7);
}

*Исходный код примера

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

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


В данном случае схлопывание работает немного запутаннее и сложнее, чем в примерах выше. Для начала нужно знать, что отсупы блоков схлопываются соответствующими полями. Это значит, что верхнее поле родителя схлопнется с верхним полем своего потомка. Для рассмотрения данной ситуации представим, что имеется три блока: внешний блок-родитель, блок-родитель и дочерний блок. Для того, чтобы сработало схлопывание, нужно что бы блок-родитель не имел ни полей, ни границ (их значения были нулевые). Таким образом, внешние поля этих элементов соприкасаются. Заметьте, что нулевые значения внутренних полей и границ для родителей — обязательное условие срабатывания схлопывания.



.outside_parent {
    margin-top:40px;
    margin-left: 40px;
    border:2px dotted black;
    background:rgba(150, 0, 255, 0.7);
}

.parent {
    padding:0px; /* Обратите внимание что внутренние отступы равны 0 */
    border:0px solid grey; /* Полей у родителя так же не должно быть */
    margin-top: 40px; /* Этот отступ схлопнулся с дочерним */
    background:rgba(150, 0, 255, 0.7); 
}

.daughter {
    width: 196px;
    margin-top:60px; /* Актуальное значения отступа */
    border:2px solid white;
}

*Исходный код примера

Напомню, что размер одной клетки равно 20 пикселей, поэтому без схлопывания отступ между элементами был бы 60+40 пикселей, но из-за применения данного эффект он равен максимальному значению одного из элементов, в данному случае — 60 пикселям(3 клетки). Видим, что отступ дочернего блока заменяет верхний отступ для своего родителя. Если для родительского элемента задать что-либо: поле (padding) или же границу (border), то схлопывание не применится и расстояние между первым блоком и вторым будет очевидные 100 пикселей. Таким способом, можно управлять наличием данного эффекта, добавляя маленькое поле, либо же бесцветную границу, допустим, толщиной 1 пиксель, тем самым отключая эффект схлопывания.
На первый взгляд может показаться, что эфект работает только при формировании структуры как в примере выше, но это не так: схлопывание сработает и между соседними элементами, когда у них вложенные другие блоки. Рассмотрим ситуацию, когда имеется внешний блок и два блока, один с которых вложенный в другой.



.outside {
    height:16px;
    margin: 20px 0 0 40px;
    border:2px solid white;
    background:rgba(150, 0, 255, 0.7);
    margin-bottom:20px; /* Этот отступ так же схлопнулся */
}

.parent {
    margin-top:40px; /* Этот отступ схлопнулся с дочерним */
    margin-left:40px;
} /* Родительский блок не должен иметь ни полей ни границ для срабатывания эффекта */

.daughter {
    margin-top:60px; /* Актуальный схлопнувшийся отсуп */
    border:2px solid white;
}

*Исходный код примера

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

Вывод


Для хорошего специалиста важно контролировать все надлежащие ему инструменты и атрибуты: хорошего механика волнует не только количество клапанов в двигателе, но и наличие хорошей системы впрыска топлива и качество самого топлива. Так и хороший веб-разработчик должен контролировать каждый пиксель и не терять важные детали в структуре сайта.

Дьявол в мелочах, дьявол в пикселях.
  • +11
  • 24,5k
  • 5
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 5
  • +3
    Вы бы добавили ссылок на jsfiddle.net, например, хотя бы. И перепроверили всё написанное там в действии перед постом.

    Например, в последнем случае у вас там явная каша и никакой логики:
    Судя по всему, первый блок стилей должен применяться ко всем блокам, а ещё там точки с запятой не хватает. Кроме того, никакого суммирования там нет и не будет, поскольку верхний отступ родительского блока совершенно логично суммируется с нулевым отступом первого блока и совершенно логично не суммируется с отступом в 40px второго вне зависимости от наличия границ у родителя.
    • +2
      Спасибо за замечания! Исправил статью, изменил изображения, добавил исходные коды с комментариями.
    • 0
      Есть идейка, чтобы и дизайнеры прочувствовали эти правила (подавляющее большинство и не слышало про это явление), сделать им плагин для Photoshop, который будет после указания полей и типа блока добавлять скрытый полупрозрачный слой в группу с элементом. Тогда будет неимоверно легко как и верстальщику верстать весь набор элементов, так и дизайнеру делать макеты на базе готовых компонентов.
      Только вот не умею плагины делать.
      Был бы крутой плагин, как, например, PNG Express и т.п.
      • 0
        Можете объяснить следующее поведение?
        Если взять последний пример и поставить margin-left: 20px; у parent, то outside сдвигается влево на 20px, а parent и daughter остаются на месте.

        *Исходный код примера
        • 0
          Оказывается все в порядке. Автор допустил синтаксическую ошибку в css не закрыв комментарий. В итоге часть правил начиная с margin-left у parent относились к классу outside.

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