1. 17. 11. Алгоритм выделения блоков

Чтобы избежать типовых ошибок использования БЭМ, предлагаем идти по следующей схеме:

Шаг 1

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

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

<section class="card">
  <h2 class="heading">Дом из мусора</h2>
  <img class="image" src="img/pic-1.jpg" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <button class="button-rent" type="button">Арендовать</button>
  <button class="button-compare" type="button">Добавить в сравнение</button>
</section>

Шаг 2

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

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img class="card__image" src="/adaptive/29/book/html/bem/algorithm/img/pic-1.jpg" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <button class="button-rent" type="button">Арендовать</button>
  <button class="button-compare" type="button">Добавить в сравнение</button>
</section>

Но всё ли так однозначно и просто в том, чтобы отделить блоки от элементов. Разберём на практике.

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

Следовало ли сделать заголовок раздела отдельным независимым блоком?

Аргументы «За» независимый блок:

  • С одной стороны, это соответствовало бы духу БЭМ: заголовки повторяются, их код идентичен.

  • Если нам скажут, что заголовки нужно увеличить, было бы удобно делать это из модуля заголовка.

Аргументы «Против» независимого блока:

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

  • Может измениться заголовок только одного раздела, в этом случае лучше, чтобы это был элемент блока Раздел.

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

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

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

У крупных проектов обычно бывает «дизайн-система» или UI-kit, в который попадают такие системные элементы, как кнопки, заголовки разных видов, карточки, списки, возможно — цитаты, другие текстовые элементы, даже виды абзацев. Если бы наш макет был для такого случая, то, вероятнее всего, заголовок был бы блоком.

Если вы хорошо разобрались, что такое блок и что такое элемент, как сочетать эти две БЭМ-сущности на одном HTML-элементе, следующие два шага можно объединить.

Шаг 3

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

Карточка первого типа:

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img class="card__image" src="img/pic-1.jpg" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <div class="card__button-rent-wrapper">
    <button class="button-rent" type="button">Арендовать</button>
  </div><div class="card__button-compare-wrapper">
    <button class="button-compare" type="button">Добавить в сравнение</button>
  </div>
</section>

Карточка второго типа:

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img class="card__image" src="img/pic-1.jpg" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <div class="card__buttons-wrapper">
    <button class="button-rent" type="button">Арендовать</button>
    <button class="button-compare" type="button">Добавить в сравнение</button>
  </div>
</section>

Шаг 4

Оптимизируем разметку блоков, используем миксование и убираем лишние обёртки.

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

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img class="card__image" src="img/pic-1.jpg" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <button class="card__button button-rent" type="button">Арендовать</button>
  <button class="card__button button-compare" type="button">Добавить в сравнение</button>
</section>

Шаг 5

Анализируем блоки/элементы, смотрим и выявляем одинаковые.

Это то действие, которое мы отложили на потом в Шаге 1.

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

Карточка первого типа:

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img src="card__image" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <button class="card__button button button--primary" type="button">Арендовать</button>
  <button class="card__button button button--secondary" type="button">Добавить в сравнение</button>
</section>

Карточка второго типа:

<section class="card">
  <h2 class="card__heading">Дом из мусора</h2>
  <img src="card__image" width="300" height="300" alt="Домик «Cabana Floripa» на вершине холма острова Флорианополис.">
  <p class="card__text">Домик «Cabana Floripa» на вершине холма острова Флорианополис создан уругвайским художником Jaime, который использовал для его постройки различный мусор, собранный в этом районе: старую древесину, бутылки, керамическую плитку, осколки зеркал.</p>
  <div class="card__buttons-wrapper">
    <button class="button button--primary" type="button">Арендовать</button>
    <button class="button button--secondary" type="button">Добавить в сравнение</button>
  </div>
</section>

И в конце покажем карточки того и другого типа на действующих сайтах.

Разберём ещё несколько случаев на практике.

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

Возможны три варианта реализации этого кейса:

  1. Взять блок Преимущества работы с нами, его модифицировать, убрать специфические свойства, превратить в базовый (абстрактный) класс section. Для свойств, которые убрали из базового класса добавить модификатор — это будет реализация для блока Преимущества работы с намиsection section--profit. Для блока Категории товаров использовать другую модификацию базового класса section section--category. Правда со временем могут возникнуть проблемы при изменении (рефакторинге), полезут баги, потребуется всё снова тестировать. Как итог, больше потраченного времени.

  2. Взять блок Преимущества работы с нами за основу, без изменений класс section. А для блока Категории товаров сделать модификатор. Появятся дублирующие свойства, которые переопределяются для блока Категории товаров класс section section--category. Из минусов — сложнее переопределять существующие стили, появится лишний код.

  3. Взять блок Преимущества работы с нами (profit), полностью его скопировать и назвать копию блоком Категории товаров (category), внести правки в стили. То есть мы просто создаём копию блока. Этот способ самый быстрый и дешёвый. Но при таком подходе сильно раздувается кодовая база.

Замечание

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

Ещё один пример, форма с настройками профиля пользователя.

Управляющие элементы: поля для ввода, выпадающие списки, многострочное поле для ввода имеют одинаковое оформление (шрифт, отступы, граница). Помимо стандартного набора свойств, эти элементы содержат и специфические, характерные только для этого элемента управления CSS-свойства. У многострочного поля для ввода есть CSS-свойство resize, которое помогает не сломать сетку (resize: vertical;). У выпадающего списка select есть отдельные свойства для стилизации стрелки вниз.

Как лучше поступить в этом случае?

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

<form class="user-settings-form" action="#" method="POST">
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">Имя</span>
    <input class="form-field__control input" type="text" name="add-review-name" id="add-review-name" placeholder="Ваше имя">
  </label>
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">Город</span>
    <select class="form-field__control select" name="user-settings-form-city" id="user-settings-form-city">
      <option value="1012">Москва</option>
      <option value="1013">Санкт-Петербург</option>
      <option value="1015">Нижний Новгород</option>
    </select>
  </label>
  ...
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">О себе</span>
    <textarea class="form-field__control textarea" name="user-settings-form-about" id="user-settings-form-about" placeholder="Ваши интересы и увлечения"></textarea>
  </label>
  ...
  <button class="user-settings-form__button button" type="submit">Сохранить изменения</button>
</form>

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

А можно использовать комбинацию блоков. Создать общий блок form-control — базовый блок для элементов управления. Он будет содержать только базовый набор CSS-правил, которые есть у всех контролов. Все тонкости стилизации отдельных элементов формы описываются в блоках соответствующих элементов управления.

<form class="user-settings-form" action="#" method="POST">
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">Имя</span>
    <input class="form-field__control form-control input" type="text" name="add-review-name" id="add-review-name" placeholder="Ваше имя">
  </label>
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">Город</span>
    <select class="form-field__control form-control select" name="user-settings-form-city" id="user-settings-form-city">
      <option value="1012">Москва</option>
      <option value="1013">Санкт-Петербург</option>
      <option value="1015">Нижний Новгород</option>
    </select>
  </label>
  ...
  <label class="user-settings-form__field form-field">
    <span class="form-field__label">О себе</span>
    <textarea class="form-field__control form-control textarea" name="user-settings-form-about" id="user-settings-form-about" placeholder="Ваши интересы и увлечения"></textarea>
  </label>
  ...
  <button class="user-settings-form__button button" type="submit">Сохранить изменения</button>
</form>

Шаг 6

Анализируем функциональность блока и следим за тем, чтобы он не стал «жадным».

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

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

<section class="features">
  <h2 class="features__title">Почему нас выбирают</h2>
  <ul class="features__list">
    <li class="features__item">Работаем по всей России</li>
    <li class="features__item">Строим по типовым и индивидуальным проектам</li>
    <li class="features__item">У нас работают профессиональные строители</li>
    <li class="features__item">Выполняем витражное остекление</li>
  </ul>
</section>

...

<section class="features container theme theme--dark">
  <h2 class="features__title">Что вы получаете, если выберите нас</h2>
  <ul class="features__list">
    <li class="features__item">Низкие цены и широкий ассортимент</li>
    <li class="features__item">100 лет — средний срок службы дома</li>
    <li class="features__item">Поддержка 24/7</li>
    <li class="features__item">Выгодные расценки и система скидок</li>
  </ul>
</section>

Руководствуемся правилом: «Лучше добавить лишние классы, а потом удалить, если они не понадобятся».

Last updated