Это явление по-английски называется «margin collapsing». Вот ссылка на статью (перевод на русский язык), в которой рассказывается о том, что это такое, а также о причинах этого явления:
https://developer.mozilla.org/ru/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing По-английски (в оригинале) эта статья называется «Mastering margin collapsing», что на русский язык можно перевести как «Понимание причин схлопывания внешних отступов».
В статье по ссылке не приводят способов избавиться от рассматриваемого явления.
В данном посте я не собираюсь рассматривать все случаи, в которых может произойти данное явление. Я рассмотрю один конкретный случай и несколько способов избавиться от схлопывания внешних отступов в рассматриваемом случае. Я пользуюсь браузером «Microsoft Edge» на движке «Chromium».
* * *
Итак, сначала разберемся, что подразумевается под «схлопыванием внешних отступов». Возьмем конкретный случай, тело тестовой HTML-страницы представляет следующий код:
Установим для HTML-элемента div фон и высоту в его стиле в заголовочной части этой HTML-страницы, чтобы сделать этот HTML-элемент видимым (без содержимого этот HTML-элемент имеет высоту 0 пикселей и поэтому в браузере его не видно):
У меня в браузере это выглядит так (я увеличил снимок экрана в графическом редакторе в 8 раз, чтобы можно было рассмотреть попиксельно):
Серые линии сверху и слева - это уже часть окна браузера. Я оставил их на картинке, чтобы можно было понять, где находится HTML-элемент div относительно границ области просмотра браузера. Правая и нижняя части области просмотра частично выходят за правую и нижнюю границы картинки, поэтому их не видно (мне был интересен только левый верхний угол области просмотра браузера). Квадратных розовых пикселей в оригинале не было, я дорисовал их в графическом редакторе, чтобы можно было посчитать расстояние от HTML-элемента div до границ области просмотра браузера.
Как видно на картинке, расстояние между HTML-элементом div и границами области просмотра браузера составило 8 пикселей.
Откуда они взялись, если ни у HTML-элемента div, ни у родительского HTML-элемента body в стилях не описаны какие-либо отступы? Я об этом
уже писал, для HTML-элемента body в моем браузере определен по умолчанию внешний отступ margin размером в 8 пикселей.
Хорошо, определим для HTML-элемента div свой внешний отступ размером в 5 пикселей. Внесем изменение в стили:
Вот что получилось у меня в браузере (увеличение в 8 раз остается):
Как видно на картинке, теперь расстояние от левой границы области просмотра браузера до HTML-элемента div составляет 13 пикселей, а от верхней границы области просмотра браузера до HTML-элемента div составляет те же 8 пикселей, что и для предыдущего случая.
Что произошло? Почему эти расстояния разные? В случае расстояния слева всё понятно: произошло сложение внешних отступов родительского HTML-элемента body (8 пикселей) и вложенного в него HTML-элемента div (5 пикселей), в результате чего получилось 13 пикселей.
В случае же расстояния сверху произошло то самое (заявленное в начале поста) явление схлопывания внешних отступов. В данном случае произошло схлопывание внешних отступов родительского HTML-элемента body и вложенного в него HTML-элемента div (это лишь одна из ситуаций, в которых может возникать схлопывание внешних отступов).
В случае схлопывания внешних оступов разных HTML-элементов остается размер лишь одного из схлопывающихся внешних отступов. При этом из этих внешних отступов берется больший по размеру. В данном случае больший по размеру внешний отступ оказался у HTML-элемента body, поэтому именно он и остался - 8 пикселей. И тут не имеет значения, какой из этих HTML-элементов родительский, а какой - вложенный.
Почему же слева тоже не произошло схлопывания внешних отступов? Дело в том, что схлопывание внешних отступов работает только по вертикали, по горизонтали оно не происходит.
Зачем придумали схлопывание внешних отступов по вертикали? Такое схлопывание полезно, например, для параграфов текста, то есть для отображения HTML-элементов p (это совершенно другая ситуация, в которой действует схлопывание внешних отступов: в этой ситуации схлопываются внешние отступы соседних, примыкающих друг к другу, HTML-элементов, а не вложенных, как в нашем случае). Между соседними параграфами текста (если они заключены в HTML-элементы p) браузер отображает расстояние размером только в один внешний отступ, а не в два, как следовало бы по логике: один внешний отступ от первого параграфа, второй внешний отступ - от примыкающего к первому второго параграфа.
* * *
Как избавиться от такого вот схлопывания внешних отступов по вертикали, если это нам потребуется (то есть нам нужно, чтобы в рассматриваемой ситуации были видны оба внешних отступа: от родительского и от вложенного HTML-элементов)? В интернетах можно найти много статей по этому поводу, но мне понравились ответы на соответствующий вопрос по следующей ссылке:
https://stackoverflow.com/questions/19718634/how-to-disable-margin-collapsing В первую очередь там советуют попробовать добавить родительскому HTML-элементу (для нашего случая) в стиль границу border в 1 пиксель, либо внутренний отступ padding в 1 пиксель. Это, конечно, работает (я проверил), но создает проблему, если граница или внутренний отступ нам строго не нужны.
Во-вторых, там предлагают попробовать добавить родительскому HTML-элементу (для нашего случая) в стиль определение свойства overflow со значением, отличным от visible (последнее является значением по умолчанию для этого свойства). Например, можно добавить в стиль указание overflow: auto; или overflow: hidden;.
Для нашего случая это неожиданно не сработало. Почему? Ответ можно найти по следующей ссылке:
https://stackoverflow.com/questions/41506456/why-body-overflow-not-working Именно в случае, если родительский HTML-элемент является HTML-элементом body и мы хотим избавиться от схлопывания внешних отступов по вертикали в рассматриваемом случае, то для HTML-элемента html тоже следует определить свойство overflow со значением, отличным от visible, например, так:
Ну а лично мне для избавления от схлопывания внешних отступов по вертикали в рассматриваемом случае больше всего понравился следующий вариант:
Вроде бы, тут мы тоже добавляем в родительский HTML-элемент внутренний отступ. Но только теперь размер этого внутреннего отступа не 1 пиксель, как было предложено ранее, а 0,05 пикселя.
Что это нам дает? То, что мы определили внутренний отступ у родительского HTML-элемента, убирает схлопывание внешних отступов по вертикали родительского и вложенного HTML-элементов. Но когда браузер начинает рассчитывать окончательные размеры и отступы HTML-элементов для их отображения, он округляет полученные значения до целого, так как область просмотра браузера всё равно имеет размеры в целых пикселях и отобразить в ней что-либо можно только в целых пикселях (хотя для промежуточных расчетов браузер может использовать дробные значения в пикселях). Из-за того, что значение отступа слишком мало (0,05 пикселя), оно просто теряется при округлениях и никакого отступа в браузере в итоге не отображается. А нам это и нужно.
Источник:
https://stackoverflow.com/a/33132624 Однако, не факт, что такое решение будет работать во всех браузерах. Или что это решение будет работать в будущем в тех браузерах, в которых оно работает сейчас. То есть, как я понимаю, это решение построено, исходя не из стандарта, а из конкретной реализации браузера, что не есть хорошо для надежности кода.
Дополнение от 16 августа: решение с padding: 0.05px; мне разонравилось, как только я начал применять его на практике. Хоть оно и работает, но при тестировании решений задач, когда я вывожу размеры разных HTML-элементов в лог с помощью функции console.log, выводятся дробные значения, что мешает отладке (сложнее понять, что происходит в скрипте).