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 и применить соглашение об именах, которое разрешает глобальное использование.