Связка HTML и CSS (CSS в большей степени) всегда казалась мне несколько «туманной», хуже всего поддающейся контролю и тестированию. Я придумывал для себя различные правила и пытался так или иначе стандартизировать свой подход, но не было ощущения, что «вот, это оно». Я несколько раз мельком знакомился с БЭМ (и не только), читал статьи на эту тему, но дальше чтения дело не заходило. Но чем дальше, тем сильнее было ощущение необходимости в наличии определенной строгой методологии. В конце концов, я решил попробовать внедрить БЭМ на одном из своих проектов, где без этого, на мой взгляд, было не обойтись. Речь идет о CMS, упрощенную страничку бекенда которой я приведу в качестве примера верстки:



Сразу хочу заметить, что БЭМ — это далеко не методология на все случаи жизни, и вопрос о необходимости ее применения в том или ином проекте следует рассматривать в частном порядке (в том числе исходя из того, нравится она вам или нет). Также, в силу того, что я не использовал предлагаемую специфическую файловую структуру или генерацию HTML, о них говорить не будем (позднее я все-таки разделил CSS-файл на отдельные части, соответствующие блокам, но этим решил пока ограничиться). Также, уже достаточно много (например, вот и вот) написано о достоинствах и недостатках этого подхода в целом, поэтому говорить об этом тоже не будем, я просто поделюсь своим опытом и размышлениями на эту тему, предполагая, что с сутью вы уже знакомы.

Приступаем


Итак, в моем случае CMS — большой и модульный проект с открытым исходным кодом, в котором каждый модуль может иметь собственные куски бекенда, используя общие и добавляя к ним специфические элементы интерфейса. В идеале, модули должны разрабатываться и другими разработчиками. Думаю, это как раз такой проект, где применение БЭМ (или другой серьезной методологии) является не просто уместным, а необходимым. Особенно это важно, т. к. многие из веб-разработчиков считают CSS побочной, недостойной внимания и глубокого изучения технологией, пытаясь добиться необходимого результата путем копирования готовых кусков разметки и стилей, концентрируясь на том, что им больше нравится, получая в итоге неподдерживаемые таблицы стилей ужасающего качества.

На первых порах проще всего было вообще не трогать исходный код моего проекта, т. к. он достаточно крупный. Вместо этого я создал простой HTML-документ (/index.html) и таблицу стилей (/css/style.css) для него, положил все это на рабочий стол для удобства и решил для начала сверстать несколько фрагментов с картинки выше, используя для этого Notepad++ и браузер. (В результате я хотел получить страницу, содержащую вообще все необходимые мне составные части, и уже затем, в случае успеха, перенести это в свой проект. Упрощенный результат доступен для изучения по ссылке в конце статьи; ссылку на то, как все вышло в реальности, тоже можно глянуть там.)

Кнопки


Я решил начать не со структуры, а с маленького блока кнопки — button. Кнопки у меня бывают 3-х типов: позитивное действие, негативное действие и нейтральное действие. Отличаются они лишь цветом, поэтому эти отличия я описал в виде булевых модификаторов, соответственно, button--positive, button--negative и button--neutral (я выбрал альтернативный синтаксис для модификаторов и вместо одного символа подчеркивания использую два дефиса — для меня это выглядит значительно нагляднее).

В результате в HTML кнопка описывается таким образом:

<button class="button button--positive" type="button">Text</button>

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

<a class="button button--neutral" href="#">Cancel</a>

Выглядит вполне читаемо и понятно. Посмотрим теперь на CSS:

.button {
  border: none;
  cursor: pointer;
  font: normal 15px 'PT Sans', sans-serif;
  line-height: 20px;
  display: inline-block;
  padding: 5px 10px;
}

Кнопка описана очень просто. Хотя я и встречал рекомендации сбрасывать значения всех правил внутри своих классов (чтобы на них не влияло окружение), но, как по мне, это уже слишком, и требуется лишь тогда, когда есть реальный шанс, что вы будете повторно использовать ваш блок в каком-то другом проекте, где стиль верстки отличается от вашего (например, вы разрабатываете какой-то виджет, который нельзя вставить как iframe в целевую страницу). Класс блока button, как и требует БЭМ, никаким образом не специфицирует свои размеры или внешние отступы.

Идем далее:

.button--positive {
  background-color: #87b919;
  color: #fff;
}

.button--positive:hover {
  background-color: #a0d71e;
  color: #fff;
}

.button--negative {
  background-color: #ff4100;
  color: #fff;
}

.button--negative:hover {
  background-color: #ff7346;
  color: #fff;
}

.button--neutral {
  background-color: #f0f0f0;
}

.button--neutral:hover {
  background-color: #f5f5f5;
}

Эти классы определяют модификаторы для различных типов кнопок (в зависимости от действия) и их состояния при наведении на них курсора мыши.

Посмотрим на наши кнопки вживую:


По-моему, хорошо.

Группы кнопок


В моем проекте я практически нигде не использую кнопки сами по себе, они почти всегда сгруппированы в группы (например, «Сохранить» и «Отмена» в форме). В каждой группе кнопки должны быть расположены горизонтально, на расстоянии ровно в 1 пиксель друг от друга. Чтобы не испытывать затруднений с выдерживанием этого расстояния (в случае с inline- или inline-block-элементами оно зависело бы от форматирования HTML, а именно, от наличия пробела между тегами), проще всего добавить кнопкам правило float: left, но только тогда, когда кнопка является элементом группы кнопок (т. е. само собой было бы неверно добавлять это правило непосредственно блоку button).

Итак, опишем блок группы кнопок buttons с единственным элементом buttons__button, представляющим кнопку, входящую в группу. Тогда HTML группы кнопок будет выглядеть вот так:

<div class="buttons">
  <button class="buttons__button button button--positive" type="button">Send</button>
  <a class="buttons__button button button--neutral" href="#">Cancel</a>
</div>

Рассмотрим CSS:

.buttons {
}

Класс блока buttons пуст.

.buttons::after {
  content: '';
  display: block;
  clear: both;
}

Поскольку к кнопкам внутри группы будет применяться правило float: left (причину я описал выше), я отменяю обтекание таким образом. Кстати, этот способ закрытия потока обтекания float нравится мне больше всего, хотя в устаревших браузерах он и не будет работать (чаще всего ориентироваться на них нет необходимости). В любом случае, не в этом суть.

.buttons__button {
  float: left;
  margin-right: 1px;
}

Здесь мы непосредственно описываем элемент-кнопку, входящую в группу, с отступом в одну точку справа.

.buttons__button:last-child {
  margin: 0;
}

Последняя кнопка в группе не должна иметь отступа справа, используем псевдокласс :last-child для этого. Я использовал именно псевдокласс, а не модификатор, т. к. все без исключения кнопки в группах, если они расположены последними, не должны иметь этот отступ справа. Считаю использование модификаторов в таком случае излишним.

На мой взгляд, получается достаточно здорово. Сами по себе блоки никак не позиционируют себя, не описывают внешних отступов. Но когда мы помещаем блок в другой блок, он как бы одновременно становится элементом своего блока и именно класс элемента позволяет дополнительно специфицировать все необходимые правила его расположения, если они необходимы. Кстати, я всегда располагаю классы элемента первыми, затем следуют классы модификаторов элемента, а уже затем — классы блока и его модификаторов. Это очень упрощает чтение HTML, т. к. если классов много, то сразу понятнее, что во что входит. Еще момент (на всякий случай). Порядок применения CSS-классов определяется порядком их следования в CSS-файле (а не в атрибуте class, как могло бы показаться), поэтому объявлять классы следует начинать с самых простых блоков, и в самом конце размещать блоки, отвечающие за общую структуру страницы.

Вот как наша группа кнопок выглядит в браузере:


На этом с кнопками мы почти покончили, идем дальше.

Текстовые поля и текстовые области


Далее я решил разобраться с другими элементами управления. Аналогичным образом описал блоки текстового поля text-box и текстовой области text-area (текстовую область рассматривать не будем, т. к. блоки практически идентичны — в исходниках примера можно посмотреть). Далее приведен HTML блока текстового поля. Дополнительно добавлен модификатор text-box--required, означающий, что поле является обязательным к заполнению (он добавляет красную полоску справа от поля):

<input class="text-box text-box--required" type="text" />

Соответствующие CSS-классы выглядят так:

.text-box {
  background-color: #f0f0f0;
  border: none;
  font: normal 15px 'PT Sans', sans-serif;
  line-height: 20px;
  outline: none;
  padding: 5px 10px;
  resize: none;
}

.text-box:hover {
  background-color: #f5f5f5;
}

.text-box:focus {
  background-color: #f5f5f5;
}

.text-box--required {
  border-right: 5px solid #ff4100;
}

Ничего особенного здесь нет, за исключением, повторюсь, последнего модификатора text-box--required. У текстовой области тоже есть такой, но называется он text-area--required.

Выглядит наше текстовое поле следующим образом:


Поля форм


Как и в случае с кнопками, текстовые поля и области редко применяются сами по себе в моем проекте. Чаще всего они используются в составе форм в виде полей форм (совокупность заголовка и текстового поля, например). Т. е. формы собираются из небольших готовых кусков, а не из отдельных элементов управления. Поэтому я решил добавить блок field, и описать, как ведут себя заголовки и текстовые поля и области внутри поля формы с помощью элементов field__label, field__text-box и field__text-area. В итоге HTML поля формы с текстовой областью выглядит так:

<div class="field">
  <label class="field__label label">Body</label>
  <textarea class="field__text-area text-area"></textarea>
</div>

Все просто. Еще раз обратите внимание на порядок следования классов. Сперва, например, следует field__label, а label — после него, т. к. тег label является в первую очередь элементом field__label своего блока field, а уже потом независимым блоком label. Такое единообразие очень помогает. Рассмотрим CSS:

.field {
}

Этот класс пуст. При отображении полей форм непосредственно в формах нам потребуется, чтобы между ними были вертикальные отступы, но мы опишем это в соответствующем элементе form__field блока form далее.

.field__label {
  display: block;
  margin-bottom: 1px;
}

Заголовки внутри блока field будут выводиться с новой строки и иметь отступ в один пиксель снизу.

.field__text-box {
  width: 430px;
}

.field__text-area {
  width: 430px;
  height: 190px;
}

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


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

<div class="field">
  <label class="field__label label">Subject</label>
  <div class="field__culture">
    <div class="field__culture-flag">en</div>
  </div>
  <input class="field__text-box field__text-box--multilingual text-box text-box--required" type="text" />
  <div class="field__multilingual-separator"></div>
  <div class="field__culture">
    <div class="field__culture-flag">ru</div>
  </div>
  <input class="field__text-box field__text-box--multilingual text-box text-box--required" type="text" />
</div>

Обратите внимание на набор классов текстового поля, их четыре. Давайте еще раз по ним пройдемся. Класс field__text-box определяет размеры текстового поля внутри поля формы, field__text-box--multilingual добавляет небольшой дополнительный отступ справа, чтобы символы при наборе не залезали под маркер языка, который отображается поверх текстового поля. Класс text-box определяет основные параметры текстового поля, а text-box--required добавляет красную полоску справа от поля.

Новые CSS-классы:

.field__culture {
  position: relative;
  left: 450px;
  width: 0;
  z-index: 10;
}

.field__culture-flag {
  background-color: #323232;
  color: #fff;
  cursor: default;
  font-size: 8px;
  line-height: 16px;
  text-align: center;
  text-transform: uppercase;
  position: absolute;
  left: -23px;
  top: 7px;
  width: 16px;
  height: 16px;
}

.field__multilingual-separator {
  height: 1px;
}

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

Формы


Теперь, рассмотрим блок формы form. Формы состоят из полей форм и групп кнопок, которые у нас уже описаны, но добавляют к ним вертикальные отступы с помощью классов элементов form__field и form__buttons. Вот так выглядит упрощенный HTML блока form:

<form class="form">
  <div class="form__field field">
    <label class="field__label label">Body</label>
    <textarea class="field__text-area text-area"></textarea>
  </div>
  <div class="form__buttons buttons">
    <button class="buttons__button button button--positive" type="button">Send</button>
    <a class="buttons__button button button--neutral" href="#">Cancel</a>
  </div>
</form>

А вот так выглядит его CSS:

.form {
}

.form__field {
  margin-top: 10px;
}

.form__buttons {
  margin-top: 20px;
}

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

В браузере форма целиком выглядит так:


Таблицы


Теперь займемся немного более сложным элементом — таблицей. Думаю, все знают, что таблицы следует верстать таблицами (т. к. это семантически верно и имеет хорошую поддержку браузеров), но, в случае с адаптивной версткой, иногда удобнее все-таки это делать, используя теги общего назначения div со стилями, вроде display: table. В таком случае, на мобильных устройствах горизонтальную таблицу легко превратить в вертикальный список, всячески манипулируя отображаемыми данными (что-то можно скрыть, а что-то — объединить). Как бы там ни было, для реализации таблиц в своем проекте я решил использовать table, но, отчасти в качестве эксперимента, перед этим сверстал ее с использованием div. Прелесть независимости БЭМ от тегов в том, что, заменив затем теги div на table, tr и td, мне ничего не пришлось изменять в своем CSS-файле, таблица выглядела идентично. Я привел оба варианта для сравнения.

Стандартная таблица в HTML выглядит так:

<table class="table">
  <tr class="table__row">
    <th class="table__cell table__cell--header">Cell</th>
    <th class="table__cell table__cell--header">Cell</th>
    <th class="table__cell table__cell--header">Cell</th>
  </tr>
  <tr class="table__row">
    <td class="table__cell">Cell</td>
    <td class="table__cell">Cell</td>
    <td class="table__cell">Cell</td>
  </tr>
</table>

Как видим, каждому тегу дан класс. Может показаться непривычным, зато это дает возможным безболезненно поменять table, tr и td на div и не визуально не заметить различия.

CSS таблицы:

.table {
  border-collapse: collapse;
  display: table;
  width: 100%;
}

.table__row {
  display: table-row;
}

.table__cell {
  font-weight: normal;
  text-align: left;
  vertical-align: top;
  display: table-cell;
  padding: 5px 10px;
}

.table__row:hover .table__cell {
  background: #ffff96;
}

.table__cell--header {
  background: #f0f0f0;
}

.table__row:hover .table__cell--header {
  background: #f0f0f0;
}

Как видим, для самого блока, как и для его элементов, установлены правила display: table, display: table-row и display: table-cell. Благодаря этому блок становится относительно независимым от тегов. По сути, повторюсь, не думаю, что есть смысл в этих правилах, если вы уверены, что таблица будет всегда сверстана именно стандартными табличными тегами.

Ну и наконец, посмотрим на результат вживую:


Меню


Переходим к завершающему этапу. Меню представлены блоком menu. Каждое меню может содержать несколько групп элементов меню (элемент menu__group), каждая из которых, в свою очередь, может содержать один заголовок группы элементов меню (элемент menu__group-title) и несколько элементов меню (элемент menu__item). Вот соответствующий HTML:

<div class="menu">
  <div class="menu__group">
    <div class="menu__group-title sub-title">
      Group title 1
    </div>
    <a class="menu__item" href="#">Menu item 1</a>
    <a class="menu__item" href="#">Menu item 2</a>
    <a class="menu__item" href="#">Menu item 3</a>
  </div>
</div>

Я думал над тем, чтобы сделать элементы menu__group и menu__item отдельными блоками, но не нашел аргументов в пользу такого решения: нигде больше они не используются, это привело бы лишь к увеличению количества классов.

Вроде бы все очевидно, но для наглядности приведу еще и CSS:

.menu {
}

.menu__group {
}

.menu__group-title{
}

.menu__item {
  display: block;
  padding: 5px 0;
}

В данном случае у меня пусты некоторые классы. Как видим, например, внешний вид заголовков групп элементов меню определяется общим блоком sub-title (я на нем не останавливался — посмотрите, пожалуйста, в исходниках). Необходимости в пустых классах нет (скорее, есть необходимость в их удалении). Я их решил оставить для наглядности нашего примера.

Меню само по себе выглядит так:


Общая структура


Напоследок, рассмотрим общую структуру страницы. Но перед этим я хотел бы коснуться еще одного момента. Дело в том, что в основном в статьях по БЭМ рекомендуют не иметь в CSS-файле правил, не относящихся к блокам. Т. е. общих правил, применимых ко всему документу (например, с селектором по тегу, а не по классу). Я решил не идти этим путем, т. к. в таком случае увеличивается количество правил, которые необходимо дублировать в каждом блоке или элементе. Я не вижу особой причины делать это в моем случае. Если задуматься, то все блоки в моем проекте описываются в рамках единого контекста, и он неизменяем, поэтому вполне допустимо, например, задать общий стиль для текста, и во всех блоках отталкиваться от него, т. к. они все-равно должны иметь обобщенный стиль.

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

Вернемся к общей структуре. Я решил представить ее одним блоком master-detail с двумя основными элементами: master-detail__master и master-detail__detail, отвечающими, соответственно, за левую-темную и правую-светлую части страницы.

В master-detail__master я добавил два меню. Одно меню не содержит никаких дополнительных классов элемента master-detail__master, т. к. нет нужды дополнять его какими-то CSS-правилами. Второе же меню является одновременно элементом master-detail__secondary-menu, что позиционирует его внизу элемента master-detail__master. Дополнительно, элементы этого второго меню «замиксованы» с элементом master-detail__secondary-menu-item, что придает им серый цвет.

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

Также на странице остался еще один блок — табы. Решил, что описывать их уже не имеет смысла, т. к. блок очень прост.

Ну что же, в итоге мы получим такой результат, как на первой картинке.

Выводы


Зачем я решил написать это? Когда я принялся разбираться с БЭМ у меня было много вопросов, на которые я не находил однозначных ответов. Это был некий полуфабрикат идеи. Было интересно перестроиться и взглянуть по-новому на процесс HTML-верстки, отказаться от использования каскадов и так далее. В результате я так или иначе нашел для себя решения, и мне захотелось поделиться этим опытом, чтобы постараться упростить для кого-то этот процесс «привыкания и перестроения», показав еще одну точку зрения.

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

Стану ли я применять БЭМ на мелких и простых проектах? Пока не знаю. Хотя я и не испытывал вообще никаких лишних сложностей и не заметил лишнего «напряга» из-за увеличившегося количества классов, но все-таки следование методологии требует несколько бо?льших усилий, чем при «традиционном» подходе. Хотя, вполне возможно, это из-за недостатка опыта и сноровки.

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

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


  1. 776166
    22.12.2016 14:36
    +3

    Как я проект на БЭМ переводил… и перевел

    1) Взял проект.
    2) Перевёл на БЭМ.

    the end.


  1. Odrin
    22.12.2016 15:41
    -4

    Почему для большого и сложного модульного проекта Вы выбрали устаревший и кривой костыль в виде БЭМ'а, а не компонентный подход, который сам собой напрашивается для решения подобных задач?


    1. alexkunin
      22.12.2016 15:54
      +5

      А что вы подразумеваете под компонентным подходом? На запрос «css компонентный подход» гугл первым делом выдал БЭМ. Если вы про Web Components, то разве они не значительно большая штука, нежели отдельно взятый CSS?


    1. DmitrySikorsky
      22.12.2016 16:25
      +3

      Если вы про Web Components, то несколько лет назад я читал о том, что за ними будущее. Сейчас за ними, возможно, все так же будущее, но не настоящее.

      Если говорить о БЭМ и моем проекте, то у меня многие из используемых повторно блоков генерируется соответствующими классами, стили и клиентские скрипты разнесены по отдельным маленьким файликам. Блоки легко и удобно можно добавлять, изменять и переносить из проекта в проект, они не влияют друг на друга. Т. к. я как раз и старался приблизиться к компонентному подходу. Использовал БЭМ, потому как не нашел ничего лучше.

      На самом деле, сколько статей я не читал про БЭМ, везде одни и те же комментарии и ответы на них. Думаю, чтобы понять преимущество (не совершенство, а преимущество со всеми его недостатками) таких вещей, как БЭМ, над их отсутствием, необходимо сперва в полной мере ощутить недостатки «традиционного подхода».


      1. Odrin
        22.12.2016 16:55
        -2

        Уже давно настоящее, а не будущее. Взять тот же Polymer Project — поддержка всеми браузерами в т.ч. IE 11+. React, Angular 2 — так же компонентный подход. И все они позволяют инкапсулировать стили.


        1. DmitrySikorsky
          22.12.2016 17:16
          +2

          Мне нужна максимально простая реализация, в которой максимально просто разобраться, не требующая ничего стороннего. Как минимум потому, что моя цель — чтобы над проектом работали и другие люди.

          Полимер когда-то смотрел — не понравился. Сейчас еще раз открыл, глянул верстку их семпла-магазина:



          Это читабельнее моей верстки? По-моему их страница намного проще, а верстка значительно сложнее.


          1. Odrin
            22.12.2016 17:32
            +2

            Вы смотрите не верстку, у уже отработавший код и т.к. Ваш браузер (ie/edge судя по ужасному шрифту) полноценно не поддерживает web components, используется полифил. Естественно при верстке всего этого нет и да, она гораздо читабельнее чем БЭМ.



            Хотите сказать, что «menu__group-title sub-title» удобнее и лучше читается, чем пример выше?


            1. DmitrySikorsky
              22.12.2016 17:57
              +2

              Повторюсь, мне не нравится необходимость использовать большую стороннюю либу для этой цели, поэтому я даже не стану рассматривать такой вариант. Ее мало кто знает и используют, а голый CSS знают почти все. Когда web components станут стандартом, вполне возможно, я напишу новую статью.


              1. zShift
                22.12.2016 19:08

                Попробуйте на Object Oriented CSS. Он с точки зрения понятности, рациональности и оптимизации гораздо лучше и читабельнее чем БЕМ.


                1. DmitrySikorsky
                  23.12.2016 00:07
                  +2

                  Я отдал предпочтение именно БЭМ, т. к. многие крупные проекты используют его (чаще, конечно, собственные имплементации этой концепции). Но за ссылку спасибо.


    1. tadatuta
      23.12.2016 03:03
      +2

      Есть подозрение, что под словом «БЭМ» вы понимаете что-то свое, если противопоставляете компонентному подходу.

      На bem.info в «Основных понятиях» сказано буквально следующее: «Блок — логически и функционально независимый компонент страницы, аналог компонента в Web Components. Блок инкапсулирует в себе поведение (JavaScript), шаблоны, стили (CSS) и другие технологии реализации.»


  1. softshape
    22.12.2016 16:26
    +4

    <button class="buttons__button button button--positive" type="button">Send</button>
    

    — настоящая победа синтаксиса БЭМ над здравым смыслом и чувством меры.


    1. DmitrySikorsky
      22.12.2016 16:32
      +1

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

      У меня была возможность сравнить (и, при желании, она есть и у вас) оба подхода, т. к. я решил переходить на БЭМ имея уже крупный и рабочий проект. В процессе я получил удовольствие, избавился от дублирования, получил совершенно формализированную и строго структурированную верстку и стили. У меня больше нет ощущения, что это вышло из под моего контроля. Но мне интересно, какую альтернативу вы предлагаете?


      1. DenimTornado
        22.12.2016 17:07
        +4

        А так?

        <button class="buttons__button button button--positive" type="button" data-button="button">Button</button>
        


    1. Aingis
      22.12.2016 16:38
      +1

      А что вы понимаете под «здравым смыслом и чувством меры»? А то у каждого они свои.

      Что касается именования, то оно тут не шибко удачное. Вместо buttons__button лучше бы подошло что-то вроде actions_item. Модификатор стоило бы назвать type: button_type_positive (в классической нотации модификаторы обособляются одним символом подчёркивания). И так во многих местах. В качестве примера реализации можно посмотреть на библиотеку bem-components.

      Если вы про тег <button>, то стоит напомнить, что блок button не обязательно кнопка, это может быть и ссылка, выглядящая как кнопка. (В примере она идёт как раз за процитированной вами строчкой.) Кроме того, использование классов даёт всю ту же модульность: можно параллельно вводить БЭМ и использовать старую вёрстку (если не сильно мешают каскадные селекторы, но их можно обособить), спасибо автономности блоков.


      1. DmitrySikorsky
        22.12.2016 16:45

        В моем случае блок «кнопка» подразумевает не тег «кнопка», а именно визуальный компонент «кнопка», который может быть технически и ссылкой (у меня в примере такая есть), но выглядеть должен именно как «кнопка». Отсюда такое название. Вариант actions мне совершенно не нравится, т. к. оно не отражает суть блока и может подразумевать все что угодно. Посудите сами, если вы видите ссылку с таким классом, какая ассоциация у вас возникнет? Уверен, что весьма обобщенная. Другое дело, когда блок называется «кнопка».

        Что касается синтаксиса для модификаторов, то я указал, что выбрал один из альтернативных вариантов, который, кстати, приведен на сайте bem.info. Мне он понравился больше. Там же описаны и булевые модификаторы, которые компактнее стандартных и не менее (даже более, как по мне) читабельные.


        1. Aingis
          22.12.2016 17:38
          +1

          actions — это не ссылка или кнопка, это блок с набором активных компонентов, например тулбар, у вас — ряд кнопок. actions_item — элемент блока, активная единица (миксуется к button, задаёт расположение).

          Булевы модификаторы бывают, но у вас два модификатора про одно и тоже. В итоге выходит три варианта, включая отсутствие модификатора. Это уже совсем не про булев модификатор.


          1. DmitrySikorsky
            22.12.2016 18:09
            +1

            Я понял, что вы имеете в виду насчет модификаторов. Возможно действительно стоило пояснить, что они все имеют общий смысл, таким образом. Спасибо!


  1. raveclassic
    22.12.2016 17:42
    -2

    Зачем вам бэм, когда есть модули?


    1. DmitrySikorsky
      22.12.2016 18:04

      Писал выше, что не хочу ничего слишком специфического и стороннего использовать, чтобы не повышать порог вхождения в проект. Если честно, мне и не нравится ничего, связанного с HTML/CSS, кроме голого HTML и CSS. Но вот БЭМ пришлась по душе.


  1. Evgeny42
    22.12.2016 19:36
    +4

    Мне кажется, со временем, человек, который занимается версткой, приходит к чему-то похожему на bem, лично у меня так и вышло.


  1. rumkin
    22.12.2016 20:14

    Мне жутко не нравится БЭМовские селекторы, поэтому я использую смесь из camelCase и kebab-case вида 'блок-элемент-модификатор'.


    .tableView-cell {}
    .tableView-cell-selected {}
    .userList-item {}
    .userList-item-deleted {}

    А так как CSS регистрочувствителен, то никаких проблем с этим нет. Так же иногда я прибегаю к модификаторам с префиксом в виде дефиса.


    .tableView-row.-selected {}

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


    1. DmitrySikorsky
      23.12.2016 00:12

      Я так понимаю, вы используете +- то же самое, но с немного другим синтаксисом. Думаю, БЭМ можно как раз и рассматривать как «референсную» методологию, наталкивающую на правильные мысли. Главное не в конкретной реализации и даже не в конкретной методологии, а в ее наличии и грамотной имплементации.


      1. rumkin
        23.12.2016 18:32

        Еще забыл упомянуть двубуквенный префикс для переиспользуемых css-библиотек: my-tableView-row.


    1. tadatuta
      23.12.2016 02:56
      +2

      Вы удивитесь, но среди предлагаемых на официальном сайте вариантов нейминга в том числе присутствует camelCase.


      1. rumkin
        23.12.2016 18:28

        Вы правы – удивлюсь ) Раньше этого не было.


  1. jMas
    22.12.2016 22:53
    +1

    Чистое ИМХО, но класический БЕМ выглядит перегруженным, и при написании, хоть и есть логика в именовании компонентов, но доставляет больше неудобств, чем ощущение 100% полезности. Склоняюсь к использованию более наглядных методов, которые ближе к CSS:


    • Разбивка компонентов на файлы BlockName.scss
    • Использование в этом файле префикса .BlockName для всех классов (scope)
    • Использование .BlockName.is-modifier в качестве модификатора
    • Все дочерние элементы .BlockName-element.is-modifier

    Пример: .TopNav-item.is-active выглядит по-моему лучше, нежели .top-nav__item .top-nav--is-active.
    Но да, нужно признать, это все вариации на тему БЭМ.


    1. DmitrySikorsky
      23.12.2016 00:17
      -1

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


    1. noodles
      23.12.2016 01:01
      -1

      Поддерживаю. Сам примерно так и пишу. Но к элементам внутри компонента обращаюсь через .BlockName > тег {....} или .BlockName:nth-child(){....}, не засоряя разметку классами. Т.е. класс имеет только компонент, а не все абсолютно все теги в документе. Этот корневой класс компонента создаёт как бы область видимости для всех вложенных элементов.

      По поводу модификаторов, на каком-то старом докладе про бэм задали вопрос докладчику: «зачем использавать .top-nav__item .top-nav--is-active, если можно .top-nav__item.is-active?» Так смешно было, докладчик ушёл от ответа мотивировав тем что не понял вопроса, потому как не знал разницу между .class1 .class2 и .class1.class2 )))


      1. DmitrySikorsky
        23.12.2016 01:04
        +1

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


        1. noodles
          23.12.2016 11:30

          Вот именно в монолит, только не вёрстка превращается в монолит, а компонент, который можно переносить только целиком. Другое дело, что именно обзывать компонентом/блоком; понятное дело что не кусок разметки на несколько экранов.


      1. jMas
        23.12.2016 09:31

        .BlockName > тег — во-первых как написал DmitrySikorsky это не-гибко, во-вторых производительность этих селекторов ниже. В-третьих, если применять SCSS — это превращается во что то структурированное:


        // TopNav.scss
        
        .TopNav {
          &-item {
            &.is-active { }
          }
          &-element { }
        }


        1. raveclassic
          23.12.2016 11:26
          +1

          Раз уж мы за scss и отходим от

          не хочу ничего слишком специфического и стороннего использовать
          то почему бы не вот так:
          // .TopNav.scss
          .container {
          }
          .item {
           &_active {
             composes: item; //можно и без этого, тогда на элемент нужно вешать оба класснейма
           }
          }
          


          //TopNav.jsx - не обязательно React, можно заимпортить где угодно
          import theme from './TopNav.scss';
          
          export const TopNav = props => ({
           <nav className={css.container}>
            <a className={css.item}></a>
            <a className={css.item_active}></a>
           </nav>
          });
          


          Вы получаете инкапсуляцию стилей, автогенерацию класснеймов и отсутствие коллизий.
          В зависимости от настроек бандлера, класснеймы могут быть вот такие: .TopNav__container, .TopNav__item, .TopNav__item_active. Дополнительно можно добавить небольшой хэш (5-6) символов в конец.


          1. raveclassic
            23.12.2016 11:31

            Прошу прощения за опечетку,

            import css from './TopNav.scss';
            


        1. noodles
          23.12.2016 11:41

          как раз селектор > и даёт мне уверенность, что всё будет без проблем в будущем при переносе компонента в другое место. Что касается производительности селекторов — то это какой-то боян или миф, сколько можно про это говорить на каждом шагу, хоть кто-то делал реальные замеры (на реальных проектах, а не на синтетике в 1000000 тегов), и возможно ли это впринципе?

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

          И потом в будущем при переносе компонента в другой проект, даже если имя корневого класса совпадёт с каким-то уже существующим классом в проекте — достаточно будет только изменить один раз это имя в разметке и стилях, и всё станет на свои места, и внутрянка компонента не поломается благодаря селектору >.

          Зато какая чистота в разметке — любо дорого посмотреть))


          1. jMas
            23.12.2016 12:08
            +1

            То есть вместо:


            <div class="NavBar">
              <button class="NavBar-item is-active">...</button>
            </div>

            Вы используете:


            <div class="NavBar">
              <button>...</button>
            </div>

            И стили:


            .NavBar {
              > button {
                &.is-active { }
              }
            }

            ?


            Тогда если вдруг нужно использовать вместо <button /> например <a />, нужно не только менять разметку, но и лезть править стили?


            1. raveclassic
              23.12.2016 12:17
              +1

              А вот еще пример:

              <div class="NavBar">
               <button class="NavBar-item"></button>
               <div id="а_мало_ли" class="NavBar-sub">
                <button class="NavBar-item"></button>
               </div>
              </div>
              


              Как быть? > уже не спасет


              1. DmitrySikorsky
                23.12.2016 12:44

                Думаю, подразумевается 2 >

                .NavBar > div > button
                


                Но тогда что делать, если у нас несколько кнопок на одном уровне вложенности, а внешнее оформление у них разное? Придется все-равно использовать класс, либо как-то псевдоклассом по индексу идентифицировать. Это очень жестко.


                1. raveclassic
                  23.12.2016 12:59

                  Ну то есть стили гвоздями прибиваются к верстке. Да уж, удобно…


            1. noodles
              23.12.2016 15:33

              Если вместо кнопки поменять на ссылку, то это уже изменение разметки блока, т.е. если ты лезешь в блок/компонент — то буть добр и стили подкорректируй, потому что по сути создаётся новый компонент. А моя идея компонента, чтоб можно было переносить его куда угодно, а не изменять. Ведь суть какая — сверстать один раз и пользоваться в будущем. Почему это вдруг менять кнопку на ссылку — это можно, это нормально, а вот уже в стили лезть чтоб подправить за собой — считается плохо? Ведь раз лезешь в кишки компонента — значит берёшь на себя ответственность за его новое состояние и поведение. Поэтому чтоб такого не было — и имеет смысл делать их небольшими.

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

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

              Пример:
              Кнопка с свг-иконкой и текстом в спане (допустим без спана обойтись нельзя). Определяем что это всё будет компонентом. Даём какой-нибудь корневой класс, например .main_button. Так вот зачем мне дополнительно давать классы свг и спану, если я могу написать так:

              .main_button{
                  ...
                  ...
                  ...
                  &>span{...}
              
                  &>svg{...}
              
                  &:hover,
                  &:focus{...}
              
                  &:active{...}
              
                  &:disabled{...}
              
                  &.js_showSpinner{....}
              
                  @media при необходимости
              }
              


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


              1. jMas
                24.12.2016 06:02

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


                Но если есть комопонент, и ты его создатель, а вас еще 20+ человек на проекте и каждому необходимы какие то изменения в компонентах, тогда ты становишься на проекте узким горлышком, потому что только ты можешь делать измененя в компонентах, потому что это твоя отвтетственность.


                1. noodles
                  24.12.2016 12:45
                  -1

                  Верно — сайтики и команда небольшая.

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

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

                  И да, если вдруг разработчику нетерпится что- то изменить в разметке компонента, он это делает но и без проблем может найти место в стилях по корневому классу компонента чтоб дописать их. Если влом лезть в стили, может прямо инлайново написать их как-нибудь, верстальщик за ним подправит потом, ничего плохого в этом не вижу. Но это редкая ситауция, потому как «архитектура» вёрстки продумывается достаточно хорошо.

                  И никаким узким горлышком вёрстка у нас никогда не была. Bottle neck — это как правило сам бизнес..))


          1. DmitrySikorsky
            23.12.2016 12:14

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


  1. vithar
    23.12.2016 21:57
    +1

    button--positive, button--negative и button--neutral
    В данном случае использовать булевы модификаторы плохо, потому что это даёт возможность добавить блоку все три эти модификатора одновременно.

    Тут лучше использовать модификатор ключ-значение, потому что фактически может быть только один из этих модификаторов у конкретной кнопки, т.е. кнопка может быть или positive, или negative, или neutral.

    Рассматривайте это как аналог атрибута type у input. Значение конкретного типа определяет каким именно будет этот input. Тут логика точно такая же.


    1. raveclassic
      24.12.2016 01:35

      Можете привести пример, как избежать одновременного добавления этих модификаторов?


      1. tadatuta
        24.12.2016 02:28

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

        Например, BEMHTML.


    1. DmitrySikorsky
      24.12.2016 01:53

      Согласен, выше уже писали, правильнее будет button--type_positive.


  1. youloveazamat
    27.12.2016 16:22
    -1

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


    1. DmitrySikorsky
      27.12.2016 16:30

      В нашей сфере постоянное обучение и развитие является необходимостью. Иначе, как вы сами написали, к сожалению, будет не о чем говорить с другими специалистами. Что касается получения на выходе того же самого, то это лишь внешне то же самое. Рекомендую, если интересно, почитать о БЭМ и других методологиях, чтобы глубже разобраться в причинах их необходимости. Опять же, в одиночку особого смысла ни в чем таком нет, т. к. вы сами копаетесь в своем проекте. А вот в командной работе все недостатки очень быстро становятся очевидными.


      1. zShift
        27.12.2016 19:42

        У всех амбиции зашкаливают и принуждают применять методологии вместо того чтобы просто работать…


        1. raveclassic
          27.12.2016 23:18
          +1

          Дело не в амбициях. Методология помогает выстроить процесс, а если у вас команда (иногда еще и распределенная), то «просто работа» без методологии заведет вас в тупик


  1. i-c
    30.12.2016 22:07
    +2

    Полсотни комментариев, а автору спасибо за статью никто не сказал…
    Мне казалось, что я использую БЭМ, а оказалось, что только казалось. Прочитал и как-то всё по полочкам разложилось.
    В общем, спасибо за статью!