Box Model

В этой статье я хотел бы рассказать, как правильно расставлять поля (padding) и отступы (margin) в CSS.



Прежде всего давайте вспомним определение полей и отступов согласно спецификации W3C. В боксовой модели (box model) поля — это расстояние между контентом (content) и границей блока (border). А отступы это расстояние между границей блока и границей соседнего или родительского элемента.

Таким образом, если граница и фон элемента не заданы, то нет разницы, использовать свойство padding или margin для задания отступов, но при условии, что ширина (width) и высота (height) элемента не заданы и не изменен алгоритм расчета размеров контента с помощью свойства box-sizing.

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

Теперь давайте рассмотрим как правильно расставлять поля и отступы между элементами. Возьмем в качестве примера следующий блок.

News

Это блок новостей 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;
}


Таким образом, при необходимости изменить значение полей справа и слева это нужно будет сделать в одном месте. А при добавлении нового элемента внутрь блока новостей, он уже будет иметь нужные отступы слева и справа.

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

Negative Margins

Теперь нужно задать вертикальные отступы между элементами. Для этого следует определить какой из элементов является обязательным. Очевидно что блок новостей не может существовать без списка новостей, в то же время ссылки «Другие новости» может и не быть, заголовок тоже может быть убран, например, при изменении дизайна.

Учитывая это, задаем для заголовка отступ снизу, а для ссылки «Другие новости» отступ сверху.

.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

В этом случае можно использовать следующий способ задания отступов.

.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

Collapse Margins

То есть произойдет наложение отступов, и отступ между блоками будет равен наибольшему отступу, а не сумме отступов. Этот эффект также называют «схлопыванием».

Прошу заметить, что горизонтальные отступы, в отличие от вертикальных, не «схлопываются», а суммируются. Поля (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.

Общие правила



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

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

Комментарии (41)


  1. deivan
    05.04.2016 20:25

    БЭМ радует глаз самурая :)


  1. MiXei4
    05.04.2016 20:30
    +1

    .news__list-item:not(:first-child) {margin-top: 18px;}


    1. belyan
      05.04.2016 20:38
      +2

      Можно и так. )
      Или вот еще вариант:

      .news__list-item + .news__list-item {
          margin-top: 18px;
      }
      


      1. justfly1984
        06.04.2016 17:21
        +1

        В этом случае у вас растет специфичность (specificity), что ухудшает парсинг CSS кода при создании CSSOM.

        .news__list-item:not(:first-child) в этом случае лучше чем .news__list-item + .news__list-item


        1. belyan
          06.04.2016 17:25

          Согласен. Я просто привел еще один вариант селектора.


        1. pivchanskiy
          12.04.2016 12:22

          специфичность обоих записей одинаковая


    1. skaflock
      05.04.2016 22:35

      * + .news__list-item {margin-top: 18px;}
      

      Так короче, и специфичность меньше


      1. MiXei4
        05.04.2016 22:51
        +1

        И проблем с чтением этой конструкции больше, особенно если понадобится удлинить эту цепочку.


      1. dvamain
        05.04.2016 23:39
        +5

        * — зло!


        1. skaflock
          06.04.2016 00:41

          Аргументируйте, пожалуйста.


          1. kahi4
            06.04.2016 13:10

            На стековерфлоу. Если коротко — это один из самых медленных селекторов из-за особенностей браузеров разбора селекторов.

            Вдобавок, может простреливать в очень неожиданные места.


            1. skaflock
              06.04.2016 15:05

              Во-первых, приводить в пример статьи 2009-го года, где рассматривается производительность IE7,8 — как минимум не очень корректно. В реальности хоть сколько-нибудь заметные проблемы с производительностью могут наблюдаться, наверное, в случае каскада, когда универсальный селектор стоит справа, например ".class * " (помним, что браузеры разбирают селекторы справа налево, даже по вашей ссылке это написано), и стили для данного селектора вызывают reflow страницы.

              Вот, например, есть статья немного посвежее (2012 год), показывающая, что разницы между одним универсальным селектором, и полным отсутствием селекторов — практически нет.

              Вот статья 2014 года, показывающая, что на построение DOM и layout'а тратится значительно больше времени, чем на разбор селекторов.

              Я это всё к чему — браузеры делают умные люди, и в наши дни они (браузеры) умеют оптимизировать отображение страничек намного лучше, чем во времена того же IE8. Если специально не стрелять себе в ногу, то сложность селекторов — это не первое, о чем стоит задумываться.


          1. Per_Ardua
            06.04.2016 14:09

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


            1. skaflock
              06.04.2016 14:28

              Ну, вообще говоря, это не совсем так. CSS-селекторы разбираются браузером справа налево, поэтому в случае с "* + X" браузер сначала найдет все элементы "X", а затем, увидев селектор соседнего элемента "+" — будет проверять только соседние элементы DOM'a (По моему скромному мнению, конечно. Я не разработчик браузеров, и не знаю как на самом деле всё работает).


              1. Shannon
                06.04.2016 14:54

                Вот именно потому что вы не знаете как на самом деле и не знаете как будет завтра, хак с "* + X" использовать не стоит, и лучше опираться на стандарт, а не конкретную реализацию браузера


          1. Odrin
            06.04.2016 14:09

            От чтения HTML с такими классами вытекают глаза.


            1. belyan
              06.04.2016 14:11

              Дело привычки. У меня уже не вытекают. :)


          1. Afadeev
            06.04.2016 14:09

            Наверное поэтому frontender.info/writing-efficient-css-selectors


      1. belyan
        06.04.2016 01:06

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


        1. skaflock
          06.04.2016 01:23

          В вашем комментарии — да, но в комментарии MiXei4, на который я отвечал, конструкция

          X:not(:first-child)
          

          делает ровно то же, что и предложенная мной
          * + X
          


          1. Shannon
            06.04.2016 05:22
            +1

            Может оно и короче и делает тоже самое, но имеет проблемы с методологией подхода (в рамках БЭМ, раз тут БЭМ), читаемостью и оптимизированностью (так как любое * заставит просмотреть все элементы), в то время как

            .news__list-item:not(:first-child)
            

            будет работать только с уже выбранным массивом .news__list-item
            Да и визуально сразу понятно что речь о .news__list-item в рамках которого любой элемент кроме :first-child


            1. amakhrov
              06.04.2016 14:09

              > так как любое * заставит просмотреть все элементы

              Поскольку селекторы разбираются браузером справа налево, сначала браузер все равно выберет массив `.news__list-item` (самый правый элемент селектора). Так что на производительности это не отразится.

              `*` — зло, когда он стоит справа в селекторе.


              1. Shannon
                06.04.2016 14:49

                Ждал именно этого замечания. Да, сейчас браузер разбирает справа налево, но в случае с "* + X" нет гарантии, что браузер не преобразует этот селектор перед разбором в «X + *» для каких-то своих оптимизаций
                Так же как нет гарантий, что следующее поколение движков не начнет разбирать селекторы слева направо

                Тоесть использование "* + X" это хак, который может перестать работать, поэтому я и написал, что есть проблема в том числе и с оптимизированостью, потому что условно браузер должен просмотреть все элементы, но так как мы знаем, что браузер разбирает справо налево, то мы абузим «недокументированную» фичу, если продолжаем так делать


                1. 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? Разве это имеет смысл?


                  1. 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; }
                    }
                    


  1. prishelec
    06.04.2016 00:23
    +2

    В свое время пока не прочел книгу
    CSS. Каскадные таблицы стилей. Эрика Мейера, часто мучился при верстке. Из тех книг что читал нигде не было указано что входит в высоту и ширину блока.


  1. andrievski88
    06.04.2016 01:02
    +2

    Думаю любой верстальщик, должен это знать, для себя ничего нового, разве, как напоминание, за что в принципе тоже можно сказать спасибо!


    1. anonymous
      00.00.0000 00:00


  1. babylon
    06.04.2016 01:50
    -12

    Какой идиот назвал Веб технологии ТЕХНОЛОГИЯМИ? Когда до сих пор всё делается руками. В CSS нет поддержки иерархии дисплей листов блоков как во флеше и никакого это не волнует. Не волнует отсутствие стандартных JSON подобных форматов с многострочными текстами, ссылочности и бинарности. Всем по прежнему нравится рукоблудить по клавиатуре. Чем занимаются Ярмо и Гугл непонятно?


  1. kakrostropovich
    06.04.2016 07:58
    +1

    Понравился подход с обязательностью/опциональностью блоков для установки отступов. Вроде мысль на поверхности, но как схема работы в голову раньше не приходила. Пригодится, спасибо.


  1. WarL
    06.04.2016 08:52
    +5

    Спасибо за хорошо написанную статью. Стоит так же отметить, что схлопывания еще часто называют margin collapse.


  1. alexbaum
    06.04.2016 09:57

    Наглядное и простое изложение, спасибо! С переводом терминоголоией margin и padding не везло раньше, было много путаницы.

    +100500:

    В боксовой модели (box model) поля — это расстояние между контентом (content) и границей блока (border). А отступы это расстояние между границей блока и границей соседнего или родительского элемента.


    1. Aingis
      06.04.2016 16:46
      +2

      Вот как раз с переводом здесь всё плохо. Впрочем, как и практически везде.

      CSS свою терминологию взял не с воздуха, а из сложившийся области технологии, типографики. И в типографике «поле» — это margin, а никак не «padding». Аналогия с полями страницы, которые всегда пусты. Переводить padding как «поле» — верх неграмотности и только всех запутывать.

      В свою очередь свойство padding нужно чтобы отбивать содержимое от рамки. Название происходит от глагола to pad — отбивать. Переводится, соответственно: «отбивка».

      Далее, collapsing в английском имеет несколько смыслов, но тут явно имеется в виду не уничтожение или разрушение, а другой: складывание. Так же как складываются раскладушка или складной стул (синоним folding). Соответственно, смежные поля элементов складываются в одно, «поглощая» друг друга.

      Что интересно, сколько бы я не употреблял правильные термины, никогда не было, чтобы меня поняли неправильно. И они гораздо понятнее абстрактных «отступов». (— Подожди-подожди, какой отступ? Который маржин или паддинг?)

      Итого, правильный перевод:
      · margin — поле,
      · padding — отбивка,
      · margin collapsing — сложение полей.


      1. ivvi
        06.04.2016 17:20

        «Сложение» — тоже недвусмысленный термин, его можно неправильно понять, по крайней мере в русском языке.
        Может быть, лучше «поглощение» полей?


      1. belyan
        06.04.2016 17:41

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


      1. alexbaum
        06.04.2016 17:50

        Супер, спасибо!


  1. NTP
    06.04.2016 09:57
    +1

    ИМХО, следует упомянуть еще и про такую полезнейшую вещь, как box-sizing, которая многих выручала при выходе элементов с width: 100% за пределы родительского элемента с padding-ом.
    Извините, у меня крайняя степень слепоты… А за статью спасибо, толково и подробно :)


  1. gatilin222
    06.04.2016 10:38

    Спасибо за статью, мне б это руководство, когда учился верстать)


  1. M-A-X
    06.04.2016 19:22
    -5

    1. Что это за прикол писать 2 __ в стилях?
    2. Быдлобутстрап использует неканоничный box model, поэтому он УГ :)
    3. Просьба к быдлохабралюдям хотя бы не минусовать карму, а то нет мочи часто доставлять упоротым батхерты :)

    П.С. А с минусовой кармой можно плюсовать и минусовать карму или нет?
    Если нет, то это тупость. Хитросделанные могут надрочить друг другу и сливать всем неугодным.

    П.П.С.
    Особенно достает, когда ни одного коммента на пост, а стоит 5 минусов. Но что возьмешь с дибилоидов. :)


  1. 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'ом.
    Тут стоит оговориться, наука наукой, а ежедневная работа вносит свои правки в лексикон разработчиков.


    1. belyan
      06.04.2016 22:25

      Спасибо за дополнения. Можете про первый пункт подробнее объяснить? Желательно с примерами вызова миксина.