Разбираемся с внешними отступами в CSS

Перевод статьи «How to Understand and Work With CSS Margins».

Блочная модель

CSS имеет дурную привычку вести себя не так, как ожидается. И одна из вещей, которые больше всего сбивают людей с толку, это внешние отступы (margin). Кажется, что здесь все должно быть довольно просто, но эти отступы способны приводить к очень странным проблемам.

Для людей, лишь начинающих работать с CSS, свойство margin легко становится одной из вещей, заставляющих думать, что «это идиотский, бессмысленный язык!».

Я ежедневно сталкиваюсь с подобным, как в классе, где мои ученики пытаются разобраться в проблемах с отступами, так и в разделе комментариев на моем YouTube-канале.

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

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

Это раздражает.

В этой статье я надеюсь пролить свет на некоторые вопросы работы margin-ов. Мы рассмотрим несколько распространенных проблем, возникающих при применении этого свойства, а также простые решения этих проблем.

Что такое margin?

Прежде чем двинуться дальше, я хотел бы удостовериться, что все мы знаем, что из себя представляют эти самые margin-ы!

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

MDN очень хорошо объясняет, что такое margin:

«Margin это самый внешний слой, окружающий контент, padding и border. Это пустое пространство между блоком, к которому margin относится, и другими элементами. Размер этого пространства контролируется свойством margin и его значениями».

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

Стили браузера (user-agent stylesheets)

Браузеры по умолчанию имеют удивительно большое количество CSS, т. н. user-agent stylesheets. Именно благодаря этим стилям элемент <h1> будет отображаться крупнее, чем <h2>, даже если в нашем документе никакого CSS не будет. И именно этим стилям мы обязаны тем, что у элемента <body> есть свойство margin, которое нам приходится постоянно удалять.

Эти стили имеют большое значение, но при этом их наличие приводит к одной из самых больших проблем в использовании внешних отступов. Дело в том, что по умолчанию не все наши элементы имеют нулевые margin-ы, и, как мы вскоре узнаем, это приводит к разнообразным и странным проблемам.

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

По умолчанию для левого и правого внешнего отступа текстового элемента установлено нулевое значение, а вот для margin-top и margin-bottom — нет.

Я часто говорю людям, что эти дефолтные значения сверху и снизу, если брать грубо, такие же, как font-size элемента. Это верно для абзацев, а также заголовков от <h3> до <h6>. Для <h1> это 0.67em, а для <h2> — 0.83em.

Все вышесказанное означает, что между нашими элементами на странице существует пустое пространство, даже если явно мы отступов не задавали.

К дефолтным значениям мы еще вернемся.

Схлопывание внешних отступов

Схлопывание отступов часто бывает причиной головной боли.

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

Это само по себе странное поведение, но добавьте к этому еще и тот факт, что речь идет исключительно о вертикальных отступах (верхнем и нижнем). Это часто путает и раздражает людей.

Увидеть все это в действии можно на следующем примере:

p {
  font-size: 18px;
  margin-bottom: 40px;
}

.links {
  margin-top: 40px;
}

Чтобы проиллюстрировать происходящее, в нашем примере класс .links добавлен к последнему абзацу (<p class=»links»>), который содержит в себе две ссылки.

Когда люди делают что-то подобное, они ожидают, что отступ между средним абзацем и ссылками будет равен 80px (40px + 40px), но в итоге оказывается, что он равен 40px. Два внешних отступа соприкасаются, а потому комбинируются в один отступ (схлопываются).

Чтобы эффект стал еще заметнее, давайте установим значение margin-bottom элемента <p> в 100px:

p {
  font-size: 18px;
  margin-bottom: 100px;
}

.links {
  margin-top: 40px;
}

Опять же, два отступа не просто складываются, они схлопываются в один отступ шириной в 100px.

И это хорошо

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

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

Но хорошо это не всегда

Но схлопывание отступов приводит к путанице, когда margin-top первого потомка внутри элемента схлопывается с родительским margin-top.

Давайте еще раз посмотрим на наш скриншот:

Между верхом зоны просмотра и черным блоком есть пробел. И это не из-за body (потому что пробел гораздо больше отступа в 8px, который обычно бывает у body).

Угадайте, откуда появился этот отступ?

На самом деле это отступ элемента <h1> вверху черного блока.

Помните, я говорил, что стили браузера способны приводить к странным проблемам?

Чтобы пояснить, что конкретно здесь происходит, давайте зададим существенно больший margin-top заголовку h1.

.card {
  background: #000;
  color: white;
  width: 560px;
  margin: 0 auto;
}

h1 {
  font-size: 24px;
  margin-top: 100px;
}

p {
  font-size: 18px;
  margin-bottom: 100px;
}

.links {
  margin-top: 10px;
}

Я постоянно вижу, как люди пытаются опустить заголовок внутри его родителя. Но вместо ожидаемого поведения мы получаем гигантский отступ сверху всего блока!

Это происходит потому, что margin-top заголовка <h1> схлопывается с margin-top родительского элемента.

В этом случае ничто не разделяет верх элемента-потомка и элемента-родителя. Поэтому, когда мы добавляем margin-top элементу-потомку, он соприкасается с родительским margin-top. А как мы уже знаем, соприкасающиеся внешние отступы комбинируются в один отступ.

Таким образом, когда мы задаем margin элементу-потомку, он применяется к родительскому элементу.

Вот почему люди ненавидят CSS.

Аналогично, в приведенном выше коде мы задали всем абзацам margin-bottom. Этот отступ у элементов p.link соприкасается с margin-bottom элемента .card, а значит, отступы комбинируются, и margin влияет не на ссылки, а на элемент .card.

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

Дело в использовании margin не по назначению

Если мне нужно пространство между элементом .card и потомками внутри него, мне, прежде всего, вообще не стоит использовать margin.

Новички часто путают margin и padding. У меня есть правило на этот случай. Если вам нужно пустое пространство, используйте margin. Если вам нужно больше фона, используйте padding.

В нашем случае мы хотим, чтобы у элемента .card было больше фона. Поэтому нам не следует добавлять margin для потомка. Вместо этого нам нужно добавить padding для самого элемента .card.

На картинке хорошо видно, где padding, а где margin. У заголовка <h1> сверху по-прежнему есть margin, но он больше не сливается с отступом .card, потому что в качестве буфера добавлен padding. Это не дает внешним отступам .card и <h1> соприкоснуться.

Поскольку padding дает достаточное расстояние между абзацами <p> и заголовками <h1>, мы можем удалить добавленные ранее внешние отступы.

Внешние отступы схлопываются не всегда

В схлопывании внешних отступов есть исключения. Прямые потомки родителей grid и flex не имеют схлопывающихся отступов.

/Закатывает глаза/

Но есть возможность это обойти — и мы возвращаемся к стилям браузера, а которых уже говорили ранее.

Есть простой способ вообще забыть о схлопывании внешних отступов

Прежде всего, напомню о своем правиле выбора между внешним и внутренним отступом:

  • Если вам нужно пустое пространство, используйте margin.
  • Если вам нужно больше фона, используйте padding.

Это правило в большинстве случаев поможет вам избежать проблем. Но давайте добавим еще одно правило (оно даже еще полезнее):

  • Старайтесь вообще не использовать margin-top, за исключением случаев, когда это действительно необходимо.

Это правило немного конфликтует со стилями браузера, которые устанавливают margin-top и margin-bottom целому ряду элементов. Вот поэтому я часто делаю так:

h1,
h2,
h3,
h4,
h5,
h6,
p,
ul,
ol {
 margin: 0 0 1em 0;   
}

Это устраняет большое количество проблем, связанных с самовольным схлопыванием внешних отступов, а также с различиями в макете, когда где-то используется flex или grid, а где-то — нет.

(Примечание: если вы проверите код на freeCodeCamp, вы увидите, что они тоже поступают подобным образом!)

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

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

1 комментарий к “Разбираемся с внешними отступами в CSS”

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх