Пример использования методологии БЭМ в SASS

Одно из главных правил разработки гласит: "Ваш код должен быть понятным, гибким и иметь возможность повторного использования".

Этим принципам нужно следовать во всем. Методология "Блок Элемент Модификатор" является отличным примером.

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

В этой статье я постараюсь на примере изложить основные принципы методологии БЭМ. Предложить вариант файловой структуры и немного рассказать о подходе к проектированию разметки, который мы применяем в Динамике.

Прежде, чем начать

Если Вы еще не знакомы с методологией БЭМ, пожалуйста, прочтите главу "Быстрый Старт" на веб-сайте методологии, чтобы иметь представление о ней. Это займет у вас не более 5 минут.

Описание задачи

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

В Динамике, мы очень любим Drupal 8, в котором присутствуют все вышеперечисленные типы меню, и они по умолчанию имеют одинаковую структуру html, что очень удобно. Я рекомендую и вам объединить похожие по своему функциональному назначению элементы в один тип, и использовать для них одинаковую структуру кода. 

Вот такой шаблон имеет наша страница:

Example layout

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

Функционально, все отмеченные блоки, это меню. В нашем примере сущностью из набора Блок-Элемент-Модификатор блоком будет именно меню, класс у этого блока будет просто .menu.

 Элементом будет пункт меню, его класс .menu__item. Пункт, в свою очередь может быть активным, поэтому у пункта меню будет модификатор его состояния, его класс .menu__item_state_active.

Есть еще один момент. Меню бывает горизонтальным и вертикальным. Поэтому у него тоже будет свой модификатор с классом .menu_layout_horisontal.

Разметка, стили блоков и файловая структура "Flex"

Каждое меню можно представить в виде следующей html-разметки:

<!-- Блок меню -->
<ul class="menu">

	<!-- Элемент меню -->
	<li class="menu__item">
		<a href="about.html">About</a>
	</li>

	<!-- Элемент меню с модификатором state_active -->
	<li class="menu__item menu__item_state_active">
		<a href="portfolio.html">Portfolio</a>
	</li>

	<!-- Элемент меню -->
	<li class="menu__item">
		<a href="contact.html">Contact</a>
	</li>	
</ul>

Прежде чем я опишу стили, которые буду применять к данной разметке, стоит сказать пару слов о файловой структуре проекта. Мы используем SASS, поэтому выбор в сторону Flex очевиден. 

Ознакомиться с предлагаемыми структурами файлов можно здесь: БЭМ Быстрый старт - Файловая структура

Вот файловая структура Flex для нашего блока меню:

blocks/                           # Корневая директория
  menu/                           # Элементы и модификаторы
    layout/                       # Модификатор layout
      _horisontal.scss            # Значение horisontal для модификатора layout
    item/                         # Модификаторы или вложенные элементы для элемента item
      state/                      # Модификатор state для item
        _active.scss              # Значение active для модификатора state
      _state.scss                 # Модификатор state
    _item.scss                    # Элемент item
    _layout.scss                  # Модификатор layout
  _menu.scss                      # Блок menu

Громоздко? Да не то слово! Но, как и написано в руководстве БЭМ, такой код действительно очень легко использовать повторно и поддерживать. Структура для SASS несколько отличается от той, которую можно использовать в CSS, но при этом, она более гибкая.

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

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

Запомните. Ни элементы, ни блоки, ни модификаторы не привязаны друг к другу. И могут использоваться повторно в любых местах.

Теперь я приведу листинги и комментарии к ним

_menu.scss - базовый файл блока menu

.menu {
  
  // Base settings
  margin:  0;
  padding: 0;

  // Clearfix 
  &:before,
  &:after {
    content: " ";
    display: table;
  }
  &:after {
    clear: both;
  }

  // Modifiers
  @import "menu/layout";

  // Elements
  @import "menu/item";
}

В папке menu мы можем создать сколько угодно модификаторов и элементов. Может показаться что это лишнее раздувательство файловой структуры, которую можно было уместить в 10 строчек одного файла. Но мы не можем поместить все в один файл из-за того, что нам может понадобиться повторное использование кода. В таком случае мы просто скопируем файлы или папки, а не будем делать copy-paste.

Но как не запутаться, где модификатор, а где элемент?  Очень просто. Читать комментарии в базовых файлах. По ним очень легко ориентироваться.

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

menu/_item.scss - базовый файл элемента item

&__item {
  
  list-style-type:none;

  // Modifiers
  @import "item/state";
}

Обратите внимание на то, какой селектор я использую для элемента item. Я не использую полное имя menu__item, потому что я могу скопировать папку с элементом куда угодно. Например в блок grid. И тогда его полный селектор будет выглядеть как grid__item, но настройки элемента при этом останутся на месте.

menu/item/_state.scss - базовый файл модификатора state

&_state {

  @import "state/active";
}

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

menu/item/state/_active.scss

&_active {
  font-weight: bold;
}

Активные элементы по умолчанию используют жирное начертание.

menu/_layout.scss - базовый файл модификатора layout

&_layout {
  @include "layout/horisontal";
}

Все аналогично модификатору state.

menu/layout/_horisontal.scss

&_horisontal {
  > &__item {
    float:left;
  }
}

Здесь уже интереснее. Откуда это модификатор узнал про элемент? 

Да, действительно, здесь я иду на небольшой компромисс. Вспомните правило инверсии зависимостей:

Реализации зависят от абстракций, абстрацкии не зависят от реализаций.

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

Более того. По правилам методологии, ни блок не элемент не могут никак влиять на свое положение относительно других сущностей. Этими свойствами управляет контейнер.

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

Разметка макета страницы или чем отличается меню от навигации

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

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

Можно сделать меню, которое будет горизонтальным на широких экранах, но станет горизонтальным на мобильных устройствах, и будет открываться по нажатию кнопки. Просто управляйте его модификатором.  Это набор: кнопка + блок меню можно назвать навигацией.

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

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

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

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

Понимаете теперь, в чем разница? 

Еще пара слов о модификаторах

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

Я уверен, что вы работали с фреймворком Bootstrap. Там есть такое понятие, как уровни (success, warn, danger, primary и т.д.).  Они используются в сообщениях, таблицах, на кнопках и другими элементами. 

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

Кнопки, которые отвечают за важные действия раскрашиваются в синий цвет модификатором .btn-primary.

Сообщения об успешной операции окрашены в зеленый цвет модификатором .success

Представьте, что у нас есть такой модификатор (чтобы легче было читать, я приведу весь исходный код в одном листинге):

/*! level/_warn.scss */
&_warn {
  background: red;
  color: white;
}

/*! level/_success.scss */
&_success {
  background: green;
  color: white;
}

/*! level/_primary.scss */
&_primary {
  background: blue;
  color: white;
}

/*! _level.scss */
&_level {
  @import "level/warn";
  @import "level/success";
  @import "level/primary";
}

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

Вот почему мы делаем все элементы и модификаторы независимыми от блоков.

Вот почему sass удобнее чем css. Хотя, в этом вы итак не сомневались )

 

Огромное спасибо пользователю Lora Tokareva за корректировки в статье.

 

Спасибо, что прочитали это руководство. Буду благодарен за любые комментарии.

Vasiliy Vanin - Systems Architect
Василий Ванин
Системный архитектор