В этой статье я хотел бы рассказать, как правильно расставлять поля (
padding
) и отступы (margin
) в CSS.Прежде всего давайте вспомним определение полей и отступов согласно спецификации W3C. В боксовой модели (
box model
) поля — это расстояние между контентом (content
) и границей блока (border
). А отступы это расстояние между границей блока и границей соседнего или родительского элемента.Таким образом, если граница и фон элемента не заданы, то нет разницы, использовать свойство padding или margin для задания отступов, но при условии, что ширина (
width
) и высота (height
) элемента не заданы и не изменен алгоритм расчета размеров контента с помощью свойства box-sizing.В любом случае следует помнить, что поля могут быть как включены в ширину или высоту элемента, так и нет. Отступы же всегда задаются снаружи элемента.
Теперь давайте рассмотрим как правильно расставлять поля и отступы между элементами. Возьмем в качестве примера следующий блок.
Это блок новостей
news
. Он состоит из заголовка, списка новостей и ссылки «Другие новости». Дадим им следующие названия классов: news__title
, news__list
и news__more-link
.<div class="news">
<h2 class="news__title">Новости</h2>
<ul class="news__list">
<li class="news__list-item">...</li>
<li class="news__list-item">...</li>
<li class="news__list-item">...</li>
</ul>
<p class="news__more-link"><a href="...">Другие новости</a></p>
</div>
Поскольку каждый из этих элементов имеет одинаковые отступы слева и справа, то лучше задать поля родительскому блоку, а не задавать отступы слева и справа для каждого элемента в отдельности.
.news {
padding: 20px 25px;
}
Таким образом, при необходимости изменить значение полей справа и слева это нужно будет сделать в одном месте. А при добавлении нового элемента внутрь блока новостей, он уже будет иметь нужные отступы слева и справа.
Часто бывает так, что все элементы внутри блока имеют одинаковые отступы слева и справа, кроме одного, который вообще не должен иметь отступов, например, из-за фона. В этом случае для элемента можно задать отрицательные отступы. Тогда не придется убирать поля внутри блока для остальных элементов.
Теперь нужно задать вертикальные отступы между элементами. Для этого следует определить какой из элементов является обязательным. Очевидно что блок новостей не может существовать без списка новостей, в то же время ссылки «Другие новости» может и не быть, заголовок тоже может быть убран, например, при изменении дизайна.
Учитывая это, задаем для заголовка отступ снизу, а для ссылки «Другие новости» отступ сверху.
.news__title {
margin-bottom: 10px;
}
.news__more-link {
margin-top: 12px;
}
Мы могли бы достигнуть такого же внешнего результата, добавив отступы сверху и снизу для списка новостей.
.news__list {
margin: 10px 0 12px 0;
}
Но в этом случае при убирании ссылки «Другие новости» внизу остается лишний отступ. То же самое справедливо и для заголовка. Очевидно, что первый вариант является более правильным, поскольку позволяет гибко изменять внешний вид блока.
Теперь нужно задать отступы между отдельными новостями. Опять же, нужно принять во внимание, что количество новостей может меняться, и в списке может быть только одна новость.
Можно задать для каждой новости кроме первой отступ сверху, либо для каждой новости кроме последней отступ снизу. Первый вариант более предпочтителен, поскольку псевдоселектор
:first-child
был добавлен в спецификации CSS 2.1 и имеет более широкую поддержку, в отличие от псевдоселектора :last-child
, который был добавлен только в спецификации CSS версии 3.0..news__list-item {
margin-top: 18px;
}
.news__list-item:first-child {
margin-top: 0;
}
Таким образом, правильная расстановка полей и отступов позволяет гибко менять внешний вид любого блока без внесения изменений в стили и без нарушений в дизайне. Самое главное — определить, какие элементы блока являются основными (обязательными), а какие опциональными.
Иногда мы не можем опираться на обязательные элементы. Например, у нас есть всплывающее окошко
popup
, внутри которого может выводиться какой-то заголовок и текст. Причем в каких-то случаях может не быть текста, а в каких-то и заголовка. То есть оба элемента являются опциональными.В этом случае можно использовать следующий способ задания отступов.
.popup__header + .popup__text {
margin-top: 15px;
}
jsfiddle.net/onfv42mz/1
Тогда отступ появится, только если будут использоваться оба элемента. В случае же вывода только заголовка или только текста, лишнего отступа не будет.
Схлопывание вертикальных отступов
Еще один нюанс, о котором не все знают, связан с вертикальными отступами между соседними блоками. В определении отступов, которое я приводил выше, сказано, что отступ — это расстояние между границами текущего и соседнего блока. Таким образом, если мы расположим два блока друг под другом и зададим одному из них отступ снизу в
30px
, а другому отступ сверху в 20px
, отступ между ними будет не 50px
, а 30px
..block1 {
margin-bottom: 30px;
}
.block2 {
margin-top: 20px;
}
jsfiddle.net/j99btnc8
То есть произойдет наложение отступов, и отступ между блоками будет равен наибольшему отступу, а не сумме отступов. Этот эффект также называют «схлопыванием».
Прошу заметить, что горизонтальные отступы, в отличие от вертикальных, не «схлопываются», а суммируются. Поля (
padding
) также суммируются.Зная о «схлопывании» отступов, мы можем использовать эту особенность в свою пользу. Например, если нам необходимо расставить отступы для заголовков и текста внутри статьи, то для заголовка первого уровня зададим отступ снизу в
20px
, а для заголовка второго уровня отступ сверху 20px
и снизу 10px
, а для всех параграфов зададим отступ сверху 10px
.h1 {
margin-bottom: 24px;
}
h2 {
margin-top: 24px;
margin-bottom: 12px;
}
p {
margin-top: 12px;
}
jsfiddle.net/n27fms7s/1
Теперь заголовок
h2
можно расположить как после заголовка h1
, так и после параграфа. В любом случае отступ сверху не будет превышать 24px
.Общие правила
Подводя итог, я бы хотел перечислить правила, которых я придерживаюсь при расстановке полей и отступов.
- Если соседние элементы имеют одинаковые отступы, то лучше задавать их родительскому контейнеру, а не элементам.
- При задании отступов между элементами, следует учитывать, обязательный это элемент или опциональный.
- Для списка однотипных элементов — не забывать о том, что число элементов может варьироваться.
- Помнить о наложении вертикальных отступов и использовать эту особенность там, где она принесет пользу.
Комментарии (41)
MiXei4
05.04.2016 20:30+1.news__list-item:not(:first-child) {margin-top: 18px;}
belyan
05.04.2016 20:38+2Можно и так. )
Или вот еще вариант:
.news__list-item + .news__list-item { margin-top: 18px; }
justfly1984
06.04.2016 17:21+1В этом случае у вас растет специфичность (specificity), что ухудшает парсинг CSS кода при создании CSSOM.
.news__list-item:not(:first-child) в этом случае лучше чем .news__list-item + .news__list-item
skaflock
05.04.2016 22:35* + .news__list-item {margin-top: 18px;}
Так короче, и специфичность меньшеMiXei4
05.04.2016 22:51+1И проблем с чтением этой конструкции больше, особенно если понадобится удлинить эту цепочку.
dvamain
05.04.2016 23:39+5* — зло!
skaflock
06.04.2016 00:41Аргументируйте, пожалуйста.
kahi4
06.04.2016 13:10На стековерфлоу. Если коротко — это один из самых медленных селекторов из-за особенностей браузеров разбора селекторов.
Вдобавок, может простреливать в очень неожиданные места.skaflock
06.04.2016 15:05Во-первых, приводить в пример статьи 2009-го года, где рассматривается производительность IE7,8 — как минимум не очень корректно. В реальности хоть сколько-нибудь заметные проблемы с производительностью могут наблюдаться, наверное, в случае каскада, когда универсальный селектор стоит справа, например ".class * " (помним, что браузеры разбирают селекторы справа налево, даже по вашей ссылке это написано), и стили для данного селектора вызывают reflow страницы.
Вот, например, есть статья немного посвежее (2012 год), показывающая, что разницы между одним универсальным селектором, и полным отсутствием селекторов — практически нет.
Вот статья 2014 года, показывающая, что на построение DOM и layout'а тратится значительно больше времени, чем на разбор селекторов.
Я это всё к чему — браузеры делают умные люди, и в наши дни они (браузеры) умеют оптимизировать отображение страничек намного лучше, чем во времена того же IE8. Если специально не стрелять себе в ногу, то сложность селекторов — это не первое, о чем стоит задумываться.
Per_Ardua
06.04.2016 14:09Звездочка говорит браузеру, что он должен еще разок пробежаться по всем элементам в DOM. Это всегда зло.
skaflock
06.04.2016 14:28Ну, вообще говоря, это не совсем так. CSS-селекторы разбираются браузером справа налево, поэтому в случае с "* + X" браузер сначала найдет все элементы "X", а затем, увидев селектор соседнего элемента "+" — будет проверять только соседние элементы DOM'a (По моему скромному мнению, конечно. Я не разработчик браузеров, и не знаю как на самом деле всё работает).
Shannon
06.04.2016 14:54Вот именно потому что вы не знаете как на самом деле и не знаете как будет завтра, хак с "* + X" использовать не стоит, и лучше опираться на стандарт, а не конкретную реализацию браузера
belyan
06.04.2016 01:06Думаю в данном случае специфичность это хорошо, поскольку позволяет задавать разные отступы между разными элементами.
skaflock
06.04.2016 01:23В вашем комментарии — да, но в комментарии MiXei4, на который я отвечал, конструкция
X:not(:first-child)
делает ровно то же, что и предложенная мной
* + X
Shannon
06.04.2016 05:22+1Может оно и короче и делает тоже самое, но имеет проблемы с методологией подхода (в рамках БЭМ, раз тут БЭМ), читаемостью и оптимизированностью (так как любое * заставит просмотреть все элементы), в то время как
.news__list-item:not(:first-child)
будет работать только с уже выбранным массивом .news__list-item
Да и визуально сразу понятно что речь о .news__list-item в рамках которого любой элемент кроме :first-childamakhrov
06.04.2016 14:09> так как любое * заставит просмотреть все элементы
Поскольку селекторы разбираются браузером справа налево, сначала браузер все равно выберет массив `.news__list-item` (самый правый элемент селектора). Так что на производительности это не отразится.
`*` — зло, когда он стоит справа в селекторе.Shannon
06.04.2016 14:49Ждал именно этого замечания. Да, сейчас браузер разбирает справа налево, но в случае с "* + X" нет гарантии, что браузер не преобразует этот селектор перед разбором в «X + *» для каких-то своих оптимизаций
Так же как нет гарантий, что следующее поколение движков не начнет разбирать селекторы слева направо
Тоесть использование "* + X" это хак, который может перестать работать, поэтому я и написал, что есть проблема в том числе и с оптимизированостью, потому что условно браузер должен просмотреть все элементы, но так как мы знаем, что браузер разбирает справо налево, то мы абузим «недокументированную» фичу, если продолжаем так делатьpsykeonfarm
06.04.2016 17:26Честно говоря, впервые слышу, что браузер разбирает селекторы справа налево и меня это сильно удивляет. На практике часто использую такую технику для обнуления отступа последнего блока внутри компонента:
.box {
…
&__item-1 { margin-bottom: 30px }
&__item-2 { margin-bottom: 40px }
&__item-3 { margin-bottom: 25px }
> *:last-child {
margin-bottom: 0;
}
}
Выходит, в этом случае, браузер сначала найдёт все last-child теги на странице, а затем уже будет проверять какой из них находится внутри .box? Разве это имеет смысл?Shannon
06.04.2016 18:03Сначала найдёт все элементы, потом из них выберет те, которые внутри .box, потом от них возьмет :last-child
Как будет на самом деле — неизвестно, движок css современных браузеров загадка, до тех пор, пока кто нибудь не возьмется провести актуальное исследование
Одно ясно точно, если используется .box > *:last-child, и хочется привести к более БЭМ подходу, то есть несколько вариантов, например использование модификаторов:
<div class="box"> <div class="box__item box__item_1">item1</div> <div class="box__item box__item_2">item2</div> <div class="box__item box__item_3">item3</div> <div class="box__item box__item_1">item4</div> </div>
.box { &__item_1 { margin-bottom: 30px } &__item_2 { margin-bottom: 40px } &__item_3 { margin-bottom: 25px } &__item:last-child { margin-bottom: 0; } }
prishelec
06.04.2016 00:23+2В свое время пока не прочел книгу
CSS. Каскадные таблицы стилей. Эрика Мейера, часто мучился при верстке. Из тех книг что читал нигде не было указано что входит в высоту и ширину блока.
andrievski88
06.04.2016 01:02+2Думаю любой верстальщик, должен это знать, для себя ничего нового, разве, как напоминание, за что в принципе тоже можно сказать спасибо!
babylon
06.04.2016 01:50-12Какой идиот назвал Веб технологии ТЕХНОЛОГИЯМИ? Когда до сих пор всё делается руками. В CSS нет поддержки иерархии дисплей листов блоков как во флеше и никакого это не волнует. Не волнует отсутствие стандартных JSON подобных форматов с многострочными текстами, ссылочности и бинарности. Всем по прежнему нравится рукоблудить по клавиатуре. Чем занимаются Ярмо и Гугл непонятно?
kakrostropovich
06.04.2016 07:58+1Понравился подход с обязательностью/опциональностью блоков для установки отступов. Вроде мысль на поверхности, но как схема работы в голову раньше не приходила. Пригодится, спасибо.
WarL
06.04.2016 08:52+5Спасибо за хорошо написанную статью. Стоит так же отметить, что схлопывания еще часто называют margin collapse.
alexbaum
06.04.2016 09:57Наглядное и простое изложение, спасибо! С переводом терминоголоией margin и padding не везло раньше, было много путаницы.
+100500:
В боксовой модели (box model) поля — это расстояние между контентом (content) и границей блока (border). А отступы это расстояние между границей блока и границей соседнего или родительского элемента.
Aingis
06.04.2016 16:46+2Вот как раз с переводом здесь всё плохо. Впрочем, как и практически везде.
CSS свою терминологию взял не с воздуха, а из сложившийся области технологии, типографики. И в типографике «поле» — это margin, а никак не «padding». Аналогия с полями страницы, которые всегда пусты. Переводить padding как «поле» — верх неграмотности и только всех запутывать.
В свою очередь свойство padding нужно чтобы отбивать содержимое от рамки. Название происходит от глагола to pad — отбивать. Переводится, соответственно: «отбивка».
Далее, collapsing в английском имеет несколько смыслов, но тут явно имеется в виду не уничтожение или разрушение, а другой: складывание. Так же как складываются раскладушка или складной стул (синоним folding). Соответственно, смежные поля элементов складываются в одно, «поглощая» друг друга.
Что интересно, сколько бы я не употреблял правильные термины, никогда не было, чтобы меня поняли неправильно. И они гораздо понятнее абстрактных «отступов». (— Подожди-подожди, какой отступ? Который маржин или паддинг?)
Итого, правильный перевод:
· margin — поле,
· padding — отбивка,
· margin collapsing — сложение полей.ivvi
06.04.2016 17:20«Сложение» — тоже недвусмысленный термин, его можно неправильно понять, по крайней мере в русском языке.
Может быть, лучше «поглощение» полей?
belyan
06.04.2016 17:41С точки зрения корректности перевода вы правы. Но термин «отбивка» слишком типографский, я ни разу не слышал чтобы его употребляли верстальщики. В основном говорят «поля», «отступы», иногда «внешние поля».
Термин «сложение полей» не подходит, поскольку поля как раз не складываются, а берется максимальное значение. Тогда уж «наложение полей».
NTP
06.04.2016 09:57+1ИМХО, следует упомянуть еще и про такую полезнейшую вещь, как box-sizing, которая многих выручала при выходе элементов с width: 100% за пределы родительского элемента с padding-ом.
Извините, у меня крайняя степень слепоты… А за статью спасибо, толково и подробно :)
M-A-X
06.04.2016 19:22-51. Что это за прикол писать 2 __ в стилях?
2. Быдлобутстрап использует неканоничный box model, поэтому он УГ :)
3. Просьба к быдлохабралюдям хотя бы не минусовать карму, а то нет мочи часто доставлять упоротым батхерты :)
П.С. А с минусовой кармой можно плюсовать и минусовать карму или нет?
Если нет, то это тупость. Хитросделанные могут надрочить друг другу и сливать всем неугодным.
П.П.С.
Особенно достает, когда ни одного коммента на пост, а стоит 5 минусов. Но что возьмешь с дибилоидов. :)
apian
06.04.2016 22:22Дополню, уточню.
1. Рекомендую явно указывать вертикальные margin для каждого элемента, который в них нуждается, полагаться на влияние margin от соседнего элемента опасно, особенно при работе с пользовательским контентом.
Я для этого использую миксин:
@mixin ritm($valueTop, $valueBottom:$valueTop) {
@if $valueTop != 0 {
@include not(':first-child') {
margin-top: $valueTop;
}
}
@if $valueBottom != 0 {
@include not(':last-child') {
margin-bottom: $valueBottom;
}
}
}
2. Не стоит обеспечивать отступ между соседними блоками за счёт отступов дочерних элементов.
3. А отступы это расстояние между границей блока и границей соседнего или родительского элемента.
Верно, но тут есть тонкий момент. Стоит сделать оговорку, что margin является неотъемлемой частью блока, хоть и воспринимается как нечто попутное. Вопрос в том, что размеры блока, а конкретно его ширина, не всегда одно и тоже, что значение свойства width. Width — это ширина части бокса от border до border, либо размер контентной области в зависимости от боксовой модели, но размеры блока остаются неизменные, это стоит понимать.
Each box has four edges: the margin edge, border edge, padding edge, and content edge.
developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model
В ней у каждого бокса есть 4 области: margin (внешние отступы), border (рамка), padding (внутренние поля), и content (контент или содержимое).
developer.mozilla.org/ru/docs/Web/CSS/box_model
Each box has a content area (e.g., text, an image, etc.) and optional surrounding padding, border, and margin areas;
www.w3.org/TR/CSS2/box.html#box-dimensions
4. Таким образом, если граница и фон элемента не заданы, то нет разницы, использовать свойство padding или margin для задания отступов, но при условии, что ширина (width) и высота (height) элемента не заданы и не изменен алгоритм расчета размеров контента с помощью свойства box-sizing.
Для полноты картины, есть случаи, когда одинаковые значения margin и padding, даже при выше описанных условиях, производят различный результат, например margin: auto; ? padding: auto; codepen.io/matovas/pen/KzyLJZ
5. Отступы же всегда задаются снаружи элемента.
Перефразируем, «Отступы (margin) в обеих боксовых моделях (border-box и content-box), не влияют на значение width блока.»
6. Тонкости перевода, холивара ради,
— граница блока ? border блока, бордер — это border, а граница блока — это граница/край блока
— block area = область блока (читай размеры блока), но
— block area ? области ограниченной border'ом.
Тут стоит оговориться, наука наукой, а ежедневная работа вносит свои правки в лексикон разработчиков.belyan
06.04.2016 22:25Спасибо за дополнения. Можете про первый пункт подробнее объяснить? Желательно с примерами вызова миксина.
deivan
БЭМ радует глаз самурая :)