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


Сегодня мы рассмотрим:

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

Давайте посмотрим, что я вам подготовил.


▍ Мои правила при работе с отступами


При входе во фронтенд все знакомятся с тем, как установить отступ между двумя элементами. В этот момент человек узнаёт про внешние и внутренние отступы и учится применять их. Кажется, этот аспект очень простой. Наверное, вы уже думаете: «Стас, зачем ты про это рассказываешь?».


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



Как вы видите, есть список товаров и кнопка «Показать ещё». Между ними есть отступ. Давайте посмотрим в инструменты разработчика, как он задан.



Мы видим, что отступа вообще нет. Но визуально он есть! Просто он создан вследствие того, что блок со списком товаров распёрли дочерние элементы. Если быть точным, это был элемент на третьем уровне вложенности.



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


Честно я даже не знаю, как объяснить, что это плохо. Я никогда так не делал. Может у вас есть версия? Напишите, пожалуйста, в комментариях.


Но на этом не всё. Давайте ещё раз посмотрим на дочерний элемент.



Он является последним элементом в родительском блоке. Но почему-то его нижний отступ не сброшен! А вот это точно плохо.


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


По этой причине у меня есть правило. Всегда устанавливать отступы так, чтобы их не было у крайних дочерних элементов. Это можно сделать, устанавливая отступ со второго элемента. Например, если нужен отступ сверху, то код выглядит следующим образом:


.uia-list__group:nth-of-type(n+2) {
  margin-top: 1.5rem;
} 

Также хочу сказать несколько слов по альтернативным методам. Можно сначала установить отступ, а потом его сбросить у крайних элементов. Или установить отступ с помощью псевдо-класса :not(). Все эти способы мне не нравятся. В первом случае нужно создавать два правила. Второй — это чисто мой загон.


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


▍ Единый стиль установки значений для свойств margin и padding — это способ борьбы с неопределённостью


В программировании есть правила хорошего тона, нацеленные на аккуратность кода. В вёрстке они тоже есть. Сегодня хочу рассказать про установку значений для свойств margin и padding.


Начнём мы с примера. Я нашёл раздел, содержащий несколько новостей. Мы рассмотрим, как у него установлен отступ.



Запоминаем, что используется свойство margin-bottom. Идём дальше. В карточке новости есть элемент «Live». Посмотрим, как у него задан отступ.



Здесь мы уже видим свойство margin-top. Ладно, тоже запомним. Посмотрим блок с кратким описанием новости.



А тут сразу используется и padding-top, и padding-bottom. Полный комплект. Казалось бы, что нет ничего плохого в таком стиле работы с отступами. Просто любят разработчики разнообразие. А скорее всего этот код написало вообще несколько человек. Что не так?


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


Как оно работает на практике. Команда договаривается, с каких сторон будут установлены все значения для свойств margin и padding. Сначала по горизонтальной оси. Здесь происходит выбор между лево и право. Потом тоже самое делается для вертикальной оси. Выбирают между верхним и нижним отступами.


Лично мне нравится устанавливать отступы слева и сверху. В моём коде вам очень сложно будет найти отступы справа и снизу. Исключением являются ситуации, когда требуется установить отступы сразу с двух сторон или со всех.


▍ Реализация отступов для дочерних элементов с учётом их количества


Создание компонентов, которые можно использовать в разных условиях, является одной из задач по моей душе. Нравится мне запариться. Могу очень много времени на это потратить. Я к чему?


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


У него может быть несколько состояний. Первое — это только текст без иконки, второе — иконка, находящаяся перед текстом, третье — иконка, находящаяся после текста, а четвёртое — иконка без текста.


Если мы будем для установки отступов между иконкой и текстом использовать свойство margin, то нам нужны дополнительные классы. Важным условием является то, что они не должны конфликтовать между собой.


В итоге получается такая разметка.


<body>
  <!-- состояние №1: только текст без иконки -->
  <button type="button" class="uia-button">
    <span class="uia-button__text">Перейти</span>
  </button>
</body>

<body>
  <!-- состояние №2: иконка находящийся перед текстом -->
  <button type="button" class="uia-button">
    <svg class="uia-button__icon uia-button__icon--pos-before" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
    <span class="uia-button__text">Перейти</span>
  </button>
</body>

<body>
  <!-- состояние №3: иконка находящийся после текста -->
  <button type="button" class="uia-button">
    <span class="uia-button__text">Перейти</span>
    <svg class="uia-button__icon uia-button__icon--pos-after" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
  </button>
</body>

<body>
  <!-- состояние №4: только иконка -->
  <button type="button" class="uia-button" aria-label="Перейти">
    <svg class="uia-button__icon" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
  </button>
</body>

В CSS задаётся отступ с помощью свойств margin-right и margin-left.


.uia-button {
  display: inline-flex;
}

.uia-button__icon--pos-before {
  margin-right: 0.5rem;
}

.uia-button__icon--pos-after {
  margin-left: 0.5rem;
}

В этом решении мы сами продумываем ситуации, когда у нас появляется иконка рядом с текстом, и когда её не будет. Было бы здорово, если браузеры сами могли это предсказать. Если иконка есть, то отступ устанавливается. Если нет, то и отступа не надо. А знаете, так можно сделать. Есть же свойство gap.


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


Сначала уберу дополнительные классы uia-button__icon--pos-before и uia-button__icon--pos-after.


<body>
  <!-- состояние №1: только текст без иконки -->
  <button type="button" class="uia-button">
    <span class="uia-button__text">Перейти</span>
  </button>
</body>

<body>
  <!-- состояние №2: иконка находящийся перед текстом -->
  <button type="button" class="uia-button">
    <svg class="uia-button__icon" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
    <span class="uia-button__text">Перейти</span>
  </button>
</body>

<body>
  <!-- состояние №3: иконка находящийся после текста -->
  <button type="button" class="uia-button">
    <span class="uia-button__text">Перейти</span>
    <svg class="uia-button__icon" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
  </button>
</body>

<body>
  <!-- состояние №4: только иконка -->
  <button type="button" class="uia-button" aria-label="Перейти">
    <svg class="uia-button__icon" width="16" height="16" viewbox="0 0 24 24">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z"/>
    </svg>
  </button>
</body>

Далее добавим свойство gap для элемента с классом uia-button.


.uia-button {
  display: inline-flex;
  gap: 0.5rem;
}

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


Улучшу своё решение псевдо-классом :has.


.uia-button {
  display: inline-flex;
}

.uia-button:has(> :nth-child(2)) {
  gap: 0.5rem;
}

Теперь свойство gap будет добавлено, когда у элемента с классом uia-button будет больше одного прямого потомка. Другими словами, оно появится, когда будут добавлены элементы с текстом и иконкой.


▍ Заключение


Давайте подведём итог. В этой статье мы рассмотрели:

  • технику установки отступов для дочерних элементов без распирания их родителя;
  • почему нужно следить за отступами у элементов, граничащих с границами родителя:
  • преимущество единого стиля установки значения для свойств margin и padding делает;
  • альтернативный способ установки отступов на основе свойства gap;
  • способ установки свойства gap в зависимости от количества дочерних элементов.

Оставлю ссылки на все выпуски:

Спасибо за чтение!


P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.


© 2024 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

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


  1. Spaceoddity
    22.10.2024 13:04

    И снова здравствуйте))

    Нмв, вы забыли рассказать о самой распространённой "багофиче" (по незнанию это идентифицируется как "баг" и наоборот), известной как "схлопывание марджинов".


    1. melnik909 Автор
      22.10.2024 13:04

      Здравствуйте.

      К схлопыванию маржинов относится случай их выхода за пределы родителя сверху и снизу. Так, что частично я о нем рассказал.


  1. dom1n1k
    22.10.2024 13:04

    Если список одномерный и однородный, то отступ можно указывать через плюс:

    .list-item + .list-item {
      margin-top: 1rem;
    }

    Чем это хорошо?
    Семантически отступ как бы выносится на уровень выше, где ему и следует быть.
    Практически это читается легче, чем :nth-child() , потому что не нужно в голове формулы парсить.

    Если список двумерный, то скорее всего, нужно просто использовать grid и gap. Флекс для этого неважно подходит.


    1. Spaceoddity
      22.10.2024 13:04

      Если список одномерный и однородный

      А может лучше сразу:

      .list-item ~ .list-item

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

      Селекторов и их комбинаций вполне достаточно, чтобы выбрать именно те, которые подходят в каждом конкретном случае, а не жёстко привязываться к собственному практическому опыту на уровне догматизма ;)

      Мне вот "семантически" более верной кажется именно фильтрация через :not - "все, кроме...". Чем пытаться формулировать в голове цепочку "только если за элементом x непосредственно следует элемент y".

      К тому же, если вам понадобится задать отступ у всех элементов кроме последнего (не всегда же margin-top проставлять, иногда и margin-bottom нужен), здесь ваша привычка через непосредственный соседний селектор даст сбой. И получается что в одном случае у вас будет p + p, а в другом p:not(:last-child) - каша мала какая-то получится.


  1. Dadadam999
    22.10.2024 13:04

    Касаемо первых двух описанных проблем, полностью согласен. Они правда часто встречаются, правда я бы посоветовал для единообразия использовать оформление:

    margin: 0rem 0rem;
    padding: 0rem 0rem 0rem 0rem;

    Имхо так код более читаем, чем с использованием -top, -bottom, -left, -right префиксов. Вместо 2-4 строчек мы всегда имеем одну.

    Касаемо последней проблемы, то идея неплохая, но селектор с nth-child(2), имхо лучше не использовать в списках или перечислениях элементов в целом. В примере указан селектор (> :nth-child(2)) и он будет работать спору нет, но что произойдёт, когда количество элементов поменяется? А если нужно будет сделать несколько кнопок с особой иконкой, то нам через запятую перечислять все селекторы? Как по мне в таких случаях лучше использовать первоначальный вариант, но немного изменить его:

    uia-button--icon-before
    uia-button--icon-after
    uia-button

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


  1. isumix
    22.10.2024 13:04

    Я бы рекомендовал везде использовать 'gap' и только в крайних случаях прибегать к 'margin' и 'padding'


    1. Spaceoddity
      22.10.2024 13:04

      Неверный ответ.

      Margin - это margin (со всеми своими особенностями), padding - это padding, а gap - это gap. Первые два вообще свою историю из типографики, видимо, ведут. Использовать нужно именно те свойства, которые необходимо задать. А не заниматься "трюкачеством" и использовать свойства не по своему прямому назначению, пусть и с одинаковым внешним результатом. Вам напомнить блочную модель? И что будет, если padding заменить на margin?