SMACSS и BEM: как разместить модуль внутри модуля?

Примечание. Я использую слово Module, которое в BEM называется Блок. Также используйте измененное соглашение об именах BEM BLOCK__ELEMENT--MODIFIER, пожалуйста, используйте это в своем ответе.


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

.btn {
  background: red;
  text-align: center;
  font-family: Arial;

  i {
    width:15px;
    height:15px;
  }
}

И мне нужно создать модуль .popup-dialog с .btn внутри него:

.popup-dialog {
  ...
  .btn {
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

В SMACSS и BEM, как вы должны управлять позиционированием модуля внутри модуля?


В своем ответе, пожалуйста, определите правильное решение и проанализируйте также следующие подходы: (обратите внимание, что все приведенные ниже примеры основываются на или вышеприведенном CSS)


Подход 1

[переопределить исходный .btn класс внутри .popup-dialog]

CSS:

.popup-dialog {
  ...
  .btn {  // override the original .btn class
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

Разметка

<div class="popup-dialog">
  ...
  <button class="btn"><i class="close-ico"></i> close</btn>
</div>

Подход 2

[добавить подкласс внутри .popup-dialog]

CSS

.popup-dialog {
  ...
  .popup-dialog__btn {
    position: absolute;
    top: 10px;
    right: 10px;
  }
}

Разметка

<div class="popup-dialog">
  ...
  <button class="btn popup-dialog__btn"><i class="close-ico"></i> close</btn>
</div>

Подход 3

[подкласс .btn с модификатором]

CSS

.btn--dialog-close {
  position: absolute;
  top: 10px;
  right: 10px;
}

Разметка

<div class="popup-dialog">
  ...
  <button class="btn btn--dialog-close"><i class="close-ico"></i> close</btn>
</div>

Подход 4

[подкласс .btn с классом макета]

CSS

.l-dialog-btn {       // layout
  position: absolute;
  top: 10px;
  right: 10px;
}

Разметка

<div class="popup-dialog">
  ...
  <button class="btn l-dialog-btn"><i class="close-ico"></i> close</btn>
</div>

Ответ 1

С трудом справляясь с проблемой в недавнем крупномасштабном проекте, я приветствую вас, чтобы привлечь внимание к SO.

Я боюсь, что нет ни одного "правильного" решения проблемы, и это будет несколько основано на мнениях. Однако я постараюсь быть максимально объективным и дать некоторое представление о ваших четырех подходах к тому, что сработало для моей команды, а что нет.

Также я предполагаю следующее:

  • Вы знакомы с подходом SMACCS (вы читали книгу и реализовали ее, по крайней мере, в одном проекте).
  • Вы используете только (измененное) соглашение об именах BEM для ваших имен классов CSS, но не для остальной части стека разработки методологии BEM.

Подход 1

Это явно худший подход и имеет несколько недостатков:

  • Он создает плотную связь между .popup-dialog и .btn с помощью контекстных селекторов.
  • В будущем вы, вероятно, столкнетесь с определенными проблемами, предположите, что в будущем вы добавите дополнительные элементы .btn в .popup-dialog.

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

CSS

.popup-dialog {...}

.popup-dialog > .btn {
  position: absolute;
  top: 10px;
  right: 10px;
}

Подход 2

Это действительно очень близко к нашему решению. В нашем проекте мы установили следующее правило, и оно оказалось надежным: "Модуль не должен иметь внешний макет, но может макет его подмодулей". Это в значительной степени вдохновлено соглашениями @necolas из структуры SUITCSS. Примечание. Мы используем концепцию, а не синтаксис.

https://github.com/suitcss/suit/blob/master/doc/components.md#styling-dependencies

Мы выбрали второй вариант здесь и завершим подмодули в дополнительных элементах контейнера. Да, он создает больше разметки, но имеет преимущество, что мы все еще можем использовать макет при использовании стороннего контента, где мы не можем контролировать HTML (внедряем с других сайтов и т.д.).

CSS:

.popup-dialog {...}

.popup-dialog__wrap-btn {
  position: absolute;
  top: 10px;
  right: 10px;
}

HTML:

<div class="popup-dialog">
  ...
  <div class="popup-dialog__wrap-btn">
    <button class="btn"><i class="close-ico"></i> close</button>
  </div>
</div>

Подход 3

Это может показаться чистым (расширяется, а не перезаписывается), но это не так. Он смешивает макет с стилями модулей. Если стиль макета на .btn--dialog-close не будет полезен в будущем, если у вас есть другой модуль, который должен иметь другой макет для кнопки закрытия.

Подход 4

Это по существу то же самое, что и подход 3, но с другим синтаксисом. Класс макета не должен знать о содержании, которое он излагает. Также я не буду увлекаться синтаксисом l-prefix, предложенным в книге. По моему опыту это создает больше путаницы, чем помогает. Наша команда полностью отказалась от нее, и мы просто рассматриваем все как модули. Однако, если мне нужно было придерживаться этого, я бы попытался полностью абстрагировать макет из модуля, поэтому у вас есть что-то полезное и многоразовое.

CSS

.l-pane {
  position: relative;
  ...
}

.l-pane__item {
  position: absolute;
}

.l-pane__item--top-right {
  top: 10px;
  right: 10px;
}

.popup-dialog { // dialog skin
  ...
}

.btn { // button skin
  ...
}

HTML:

<div class="popup-dialog l-pane">
  <div class="l-pane__item l-pane__item--top-right">
    <button class="btn"><i class="close-ico"></i> close</button>
  </div>
</div>

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

Там у вас есть. Я предлагаю попробовать модифицированный подход 2 по причинам, указанным выше.

В надежде помочь.

Ответ 2

BEM

Если вы не изменяете .btn внутри .popup-dialog, то лучший подход лучше всего подходит.

Если вам нужны модификации .btn, в соответствии с методологией BEM вы должны использовать класс модификатора, например .btn_size_s

Если у вас есть модификация, не связанная напрямую с .btn, и вы сомневаетесь, можно ли ее повторно использовать в будущем, например, вы должны плавать .btn только в всплывающем окне, вы можете использовать mixin как .popup-dialog__btn

SMACSS

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

Если вам нужны какие-либо модификации, есть 2 способа: подклассы и селектор потомков.

Если модификация может быть повторно использована в будущем, используйте подклассы, например .btn-size-s. Если модификация тесно связана с некоторым конкретным модулем - лучше использовать селекторы потомков.

UPDATE:

Добавьте несколько моментов, чтобы очистить мой ответ:

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

Теперь позвольте мне прокомментировать другой подход и наилучшее его использование:

Подход 1. Рассмотрим следующий случай: у вас есть модуль Popup с модулем 'close' Button. Popup ничего не делать с Button, без изменений, без поплавков или маржи, его просто его дочерний элемент. В этом случае лучше всего подходит этот подход.

Подход 2. Другой случай: Popup имеет дочерний Button, но мы должны добавить лишний верхний край и float Button вправо. Поскольку вы можете видеть эту модификацию, плотно связанную с Popup, и не может быть полезной для других модулей. Такие "локальные" модификации лучше всего используют этот подход. в BEM этот подход также известен как mix

Подход 3 - Заключительный случай: Popup с дочерней кнопкой, но нам нужно больше Button, такая модифицированная кнопка может быть повторно использована и может быть полезна для других модулей и страниц. В BEM его называемый модификатор

Чтобы отметить основное различие между A2 и A3, удалите Button из Popup и поместите его в другое место. A3 будет по-прежнему влиять на Button, A2 не.

Таким образом, чтобы работать с модулем как дочерний, вы можете использовать A1 или A2, A3 следует использовать в случае модификации модуля независимо от контекста.

Ответ 3

Существует еще одно соглашение, которое может удовлетворить ваши потребности: https://ncss.io

Цель:

Предсказуемая грамматика для CSS, которая предоставляет семантическую информацию о шаблоне HTML.

  • Какие теги, компоненты и разделы затронуты
  • Каково отношение одного класса к другому

Пример:

<div class="modal-dialog">
  ...
  <div class="wrapper-button-dialog">
    <button class="button-dialog">close</button>
  </div>
</div>

Ответ 4

Во-первых, я хочу уточнить, что кнопка, по определению в BEM, является ELEMENT, а не BLOCK. Поэтому, если вы решили решить эту проблему с помощью методологии BEM, эта проблема станет немного проще.

Во-вторых, я согласен с миллионным решением (Подход 2), поскольку он определяет изменение элемента внутри блока, что является уникальным для самого блока. Однако, если элемент, подобный этой кнопке, существует за пределами блока popup-dialog, тогда вы захотите принять Approach 3 и применить соглашение об именах, которое разрешает глобальное использование.