Схлопывающиеся margin

Вертикальные margin у соседних элементов объединяются между собой, что известно под термином «схлопывающиеся margin». Само схлопывание действует на два соседних блока или родительский и дочерний блоки, при этом примыкающие отступы не суммируются, а комбинируются в один.

Для margin слева и справа схлопывание никогда не применяется.

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

Схлопывание задумывалось в первую очередь для корректного отображения текста в абзацах. Расстояние между абзацами <p> без схлопывания увеличится в два раза, тогда как верхний margin у первого абзаца и нижний margin у последнего абзаца останутся неизменными. Схлопывание гарантирует, что расстояние в абзацах везде будет одинаковым.

В примере 1 два блока располагаются один под другим, при этом у первого блока отступ снизу равен 20px, а у второго блока отступ сверху равен 10px.

Пример 1. Схлопывание margin

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .block {
    padding: 20px; /* Поля вокруг текста */
    border: 1px solid #666; /* Параметры рамки */
   }
   .block1 {
    background: #E2EDC1; /* Цвет фона первого блока */
    margin-bottom: 20px; /* Отступ снизу */
   }
   .block2 {
    background: #FCE3EE; /* Цвет фона второго блока */
    margin-top: 10px; /* Отступ сверху */
   }
  </style>
 </head>
 <body>
  <div class="block block1">Первый блок</div>
  <div class="block block2">Второй блок</div>
 </body>
</html>

Результат данного примера показан на рис. 1. Расстояние между двумя блоками будет равно не 30 пикселей, как многие ожидают, а 20 пикселей, потому что выбирается большее значение из двух margin.

Вертикальное расстояние между блоков

Рис. 1. Вертикальное расстояние между блоков

Если оба значения margin положительные, то из них выбирается наибольшее значение и оно задаётся как расстояние между блоков.

На рис. 2 приведено схематическое изображение результата схлопывания margin.

Схлопывание margin

Рис. 2. Схлопывание margin

Если один из margin отрицательный, то в этом случае происходит их складывание по правилам математики:

x + (-y) = x – y

Если один из margin отрицательный, тогда margin вычитаются.

На рис. 3 схематично приведено поведение блоков, когда верхний margin у нижнего блока отрицательный.

Один margin отрицательный

Рис. 3. Один margin отрицательный

Если полученное значение в результате суммирования окажется отрицательным, то оно будет действовать на нижний блок, соответственно, он сдвинется вверх на указанное значение. При этом возможно наложение одного блока на другой. В примере 2 показан блок с верхним отрицательным margin.

Пример 2. Отрицательный margin

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .block {
    padding: 20px; /* Поля вокруг текста */
    border: 1px solid #666; /* Параметры рамки */
   }
   .block1 {
    background: #E2EDC1; /* Цвет фона первого блока */
    margin-bottom: 20px; /* Отступ снизу */
   }
   .block2 {
    background: #FCE3EE; /* Цвет фона второго блока */
    margin-top: -10px; /* Отрицательный отступ сверху */
   }
  </style>
 </head>
 <body>
  <div class="block block1">Первый блок</div>
  <div class="block block2">Второй блок</div>
 </body>
</html>

Результат данного примера показан на рис. 4. Расстояние между двумя блоками считается как отступ снизу у верхнего блока минус отступ сверху у нижнего блока (20px-10px). В итоге расстояние будет равно 10 пикселей.

Вертикальное расстояние между блоков

Рис. 4. Вертикальное расстояние между блоков

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

Так, если отступы равны -10px и -20px, то итоговое значение будет -20px. При этом элементы будут частично перекрываться (рис. 5).

Отрицательные margin

Рис. 5. Отрицательные margin

Пустые элементы

Для пустых элементов, внутри которых нет никакого содержимого, margin-top и margin-bottom также комбинируются в один по тем же правилам, что и для соседних блоков. При этом должен ещё соблюдаться ряд условий:

  • для элемента не должен быть задан padding сверху или снизу;
  • для элемента не должен быть задан border сверху или снизу;
  • высота элемента не должна быть указана через свойство height или min-height.

Одновременное сочетание всех этих условий (нет содержимого, не указана высота, padding и border) встречается довольно редко, так что многие веб-разработчики никогда и не сталкиваются с подобным поведением. В примере 3 пустой блок применяется в качестве разделителя между цветными блоками.

Пример 3. Пустой блок

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .hollow {
    margin-top: 20px; /* Отступ сверху */
    margin-bottom: 20px; /* Отступ снизу */
   }
   .block {
    padding: 20px; /* Поля вокруг текста */
    border: 1px solid #666; /* Параметры рамки */
   }
   .block1 {
    background: #E2EDC1; /* Цвет фона первого блока */
   }
   .block2 {
    background: #FCE3EE; /* Цвет фона второго блока */
   } 
  </style>
 </head>
 <body>
  <div class="block block1">Первый блок</div>
  <div class="hollow"></div>
  <div class="block block2">Второй блок</div>
 </body>
</html>

Результат данного примера показан на рис. 6. Расстояние между двумя цветными блоками определяется пустым блоком с классом hollow. У этого блока верхний и нижний margin объединяются в один, общая высота которого будет 20 пикселей.

Схлопывание margin у пустого блока

Рис. 6. Схлопывание margin у пустого блока

Вложенные элементы

Также схлопываются margin самого блока с margin у его первого и последнего дочерними элементами. Более точно комбинируется так:

  • margin-top родителя с margin-top его первого дочернего элемента;
  • margin-bottom родителя с margin-bottom его последнего дочернего элемента.

На рис. 7 схематично показано что margin у дочернего элемента располагается не внутри родителя, а выходит за его пределы.

margin у дочернего элемента

Рис. 7. margin у дочернего элемента

Такое поведение часто можно встретить у заголовков, вроде <h1> или <h2>, которые идут первыми в блоке. У этих заголовков уже содержится margin-top и margin-bottom по умолчанию и он, объединяясь с родительским margin, влияет на отступы всего блока (пример 4).

Пример 4. margin у заголовка

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .block {
    background: #FCE3EE; /* Цвет фона блока */
   }
   h1 {
    background: #E2EDC1; /* Цвет фона заголовка */
   } 
  </style>
 </head>
 <body>
  <div class="block">
   <h1>Заголовок</h1>
  </div>
  <p>Текст снизу</p>
 </body>
</html>

Результат данного примера показан на рис. 8. margin у <h1> выходит за пределы блока и задаёт внешние отступы до и после блока.

margin у дочернего элемента

Рис. 8. margin у дочернего элемента

Отмена схлопывания margin

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

Схлопывание margin не срабатывает:

  • для элементов с абсолютным позиционированием, т. е. таких, у которых position установлено как absolute или fixed;
  • для обтекаемых элементов (для них свойство float задано как left или right);
  • для строчных или строчно-блочных элементов (для них свойство display задано как inline или inline-block);
  • для флекс-элементов (у родителя которых свойство display задано как flex или inline-flex);
  • для элемента <html>.

Схлопывание не действует на дочерние элементы:

  • если у родителя значение overflow задано как auto, hidden или scroll;
  • если у родителя на стороне схлопывания задано свойство padding;
  • если у родителя на стороне схлопывания задано свойство border.

Учтите, что свойства padding и border должны иметь размер больше нуля, к примеру, 1px.

Возьмём пример 4 и доработаем его, чтобы margin у заголовка работал внутри блока. Для этого к родительскому блоку добавляем свойство padding со значением 0.1px. На экране такая величина будет незаметна, но браузеры её понимают и схлопывание margin отменяют (пример 5).

Пример 5. Отмена схлопывания margin

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .block {
    background: #FCE3EE; /* Цвет фона блока */
    padding: 0.1px 0; /* Отменяем схлопывание */
   }
   h1 {
    background: #E2EDC1; /* Цвет фона заголовка */
   } 
  </style>
 </head>
 <body>
  <div class="block">
   <h1>Заголовок</h1>
  </div>
  <p>Текст снизу</p>
 </body>
</html>

Результат данного примера показан на рис. 9.

margin у дочернего элемента

Рис. 9. margin у дочернего элемента

Для отмены схлопывания у дочерних элементов можно использовать один из вариантов.

.block {
  /* 1. Ненулевой padding сверху и снизу */ 
  padding: 0.1px 0;
  /* 2. Скрываем всё за пределами блока */ 
  overflow: hidden;
  /* 3. Прозрачная линия сверху и снизу */
  border-top: 1px solid transparent; 
  border-bottom: 1px solid transparent;
}

Для соседних элементов схлопывание margin отменять, как правило, нет необходимости, поскольку результат достаточно предсказуем — надо только помнить о поведении margin или включать margin-top или margin-bottom лишь для одного элемента. В примере 6 расстояние между блоков регулируется значением margin-bottom у block1. Здесь этот margin единственный, поэтому никакого объединения не происходит и вертикальный промежуток между блоков равен значению margin-bottom.

Пример 6. Расстояние между блоков

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>margin</title>
  <style>
   .block {
    padding: 20px; /* Поля вокруг текста */
    border: 1px solid #666; /* Параметры рамки */
   }
   .block1 {
    background: #E2EDC1; /* Цвет фона первого блока */
    margin-bottom: 30px; /* Отступ снизу */
   }
   .block2 {
    background: #FCE3EE; /* Цвет фона второго блока */
   }
  </style>
 </head>
 <body>
  <div class="block block1">Первый блок</div>
  <div class="block block2">Второй блок</div>
 </body>
</html>

Для полноты картины приведём несколько вариантов, когда схлопывание margin у соседних блоков не работает. Данный стиль можно добавить к каждому блоку или только к одному. Ширина элемента с float определяется содержимым элемента, поэтому надо явно задать ширину блока, равную 100%. За счёт этого не ломается вёрстка, потому что нет места для обтекания элемента.

/* 1. Использование float */
.block {
  float: left; /* Обтекаемый элемент */
  width: 100%; /* Занимает всю доступную ширину */
  box-sizing: border-box; /* Ширина не учитывает padding и border */
}

Ширина строчно-блочных элементов также определяется их содержимым, поэтому данный вариант похож на предыдущий.

/* 2. Использование строчно-блочных элементов */
.block {
  display: inline-block; /* Строчно-блочный элемент */
  width: 100%; /* Занимает всю доступную ширину */
  box-sizing: border-box; /* Ширина не учитывает padding и border */
}

Класс parent надо добавить к родителю наших блоков.

/* 3. Использование флексбоксов */
.parent {
  display: flex; /* Работаем с флексами */
  flex-direction: column; /* Элементы выводятся друг под другом */
}

Перейти к заданиям

Автор и редакторы

Автор: Влад Мержевич
Последнее изменение: 12.01.2019
Редакторы: Влад Мержевич
Курс по вёрстке сайта на CSS Grid