Вероятно, все наслышаны о методологии БЭМ, разработанной Яндексом. Кому-то она нравится, кому-то нет. Кто-то считает её совершенной, кто-то — избыточной. Я считаю идею БЭМ отличной, но зачастую она используется не совсем оптимально. Отсюда появляются возгласы о её громоздкости и нелаконичности выходного кода.
Тем не менее, где-то в 2010 году я начал пробовать БЭМ в работе. В чистом виде пользоваться им было сложно по ряду причин, основными из которых являются:
- Абсолютное отсутствие лаконичности в названиях классов;
- Неоправданный отказ от многих возможностей CSS-селекторов;
- Недостаточное уделение внимания префиксам, которые могли бы оказать заметную помощь в достижении большей читабельности кода.
Из-за этих недостатков БЭМ непрерывно модифицировался мною, и в итоге получилось найти
такую схему, которая остаётся лаконичной, лёгкой в поддержке, а также подходит
для использования во всех моих проектах (от мала до велика).
В тексте статьи
я буду называть её AzaBEM, так как разработали её в AzaGroup.
Вот как выглядит среднестатистический класс при данном подходе:
Никаких b-head-stripe_theme_simple-red
здесь не будет. Только то, что указано на
рисунке. Не больше.
Вот как это может выглядеть в DOM:
Ниже — несколько слов о каждой части.
Block
Это единственная обязательная часть. Блоком является какой-либо достаточно самостоятельный объект:tagsList
— список тегов;menu
— меню;btn
— кнопка.
Element
Является частью блока:tagsList-tag
— тег в списке тегов;menu-item
— ссылка в меню;btn-icon
— иконка перед текстом в кнопке.
Modifier
Модификатор, добавляемый любому объекту:tagsList-tag_active
— активный тег;menu_bottom
— меню внизу страницы;btn-icon_folder
— иконка папки.
Одному классу можно приписывать лишь один модификатор. Если нужно больше, пишем так:btn-icon_folder btn-icon_big
— большая иконка папки.
В отличие от классического БЭМ, где названия модификаторов состоят из двух
частей (_size_big
), в AzaBEM названия модификаторов обычно выглядят проще: _big
.
В самом деле, сколько модифицированных состояний может быть, например,
у пункта меню? Штуки три: _first
, _last
, _current
. И всё. Так зачем же усложнять модификаторы и писать: _position_first
, _position_last
, _state_current
?
Prefix
Префиксы позволяют моментально определять тип объекта, а также служат неймспейсами для названий блоков, что помогает избегать конфликтов имён. Вообще, каждый может использовать такие префиксы, которые покажутся наиболее подходящими под его стиль вёрстки. Однако я пришёл к выводу, что оптимальным является использование четырёх разных префиксов, выделенных по функциональному признаку с учётом повторяемости на страницах сайта. Поясню.
l-
layout. Используется для структурных объектов страницы. Это могут быть: шапка сайта, главное меню, футер, а также любые другие части, которые «вшиты» в шаблон страницы
и имеют строго определённое место. Например, логотип рядом с главным меню, который
присутствует на всех (или многих) страницах в одном и том же месте,
будет иметь класс l-logo
.
e-
element. Любой выделенный элемент, повторяющийся на нескольких страницах. Это может быть кнопка, список тегов, социальные иконки,
JS-плагины календарика, модальных окон, туллтипов и пр. Все они повторяются на нескольких страницах,
на разных местах, в разных комбинациях.
Вероятно, в этом неймспейсе будет находиться больше всего классов, поэтому давать имена блокам с префиксомe-нужно предусмотрительно, соблюдая некоторые заранее выработанные правила. У меня правило лишь одно: названия блоков JS-плагинов начинаются с большой буквы —e-Calendar,e-Tootlip,e-Modal.
Остальные названия — с маленькой:e-btn,e-dropdown,e-table.
g-
global. Такие префиксы даются «модифицирующим классам». То есть классам, которые навешиваются на любой объект на странице и модифицируют его. К таким классам можно отнести: g-clrfix
, g-bold
, g-inlineBlock
и пр. Все они
не являются самостоятельными элементами, а лишь изменяют свойства других объектов.
p-
page. Данный префикс будет означать, что данный объект
встречается только на одной странице (или группе однородных страниц одного
раздела сайта) и не повторяется по всему сайту. Например, блок информации о
пользователе и блок друзей встречаются только на странице профиля. Желательно
такие классы хранить в отдельном CSS-файле. Об этом ниже.
Кроме того, можно создавать классы без префикса
. Такие классы
указываются не для
объектов на странице, а для само́й страницы. Обычно они устанавливаются для body
или html
.
Так работает, например, Modernizr. Итак, классы без префикса могут быть
использованы для:
- Определения типа браузера:
webkit
,ff
,ie
; - Определения возможностей браузера:
boxshadow
,no-boxshadow
; - Указания режима отображения страницы:
flexLayout
,scrolling
.
Отдельно нужно отметить ещё один префикс.
j-
javascript. Это единственный префикс, классы которого ни в коем случае нельзя стилизовать. Классы с данным префиксом используются только для доступа из JS: $loginForm.find(’.j-sendBtn’)
.
Внедрение данного префикса позволит нам избежать связывания логики и представления. Теперь мы в любой момент можем изменить вёрстку блока, сменить его имя и имена его элементов. И после всего этого, нам не понадобится править ни единой строки в js-коде, так как тот работает исключительно с j-классами.
Тем не менее, иногда всё же приходится использовать в JS не j-классы.
Во-первых, когда JS генерирует HTML.
Вышеупомянутые плагины календарика и модалок имеют классы с префиксомe-, и всё это генерируется на клиенте. Хотя если вы используете шаблоны, которые передаются с бэкенда, то тут проблем нет никаких. Главное — не забудьте проставить j-классы, и JS будет работать только с ними.
Во-вторых, когда необходимо сменить состояние объекта.
К примеру, отобразить скрытый прелоадер, добавив ему классe-loader_visible. Это допустимо, но крайне нежелательно. В таких случаях я рекомендую использовать атрибуты: просто установите значение атрибутаvisible, например, вtrue. В CSS необходимо будет указать соответствующий селектор:e-loader[visible=true] { ... }.
CSS-файлы на странице
Данный раздел предполагает, что вы используете какой-либо CSS-процессор (навроде LESS, SCSS) или как-то иначе производите сборку CSS-файлов. В этом случае у вас появляется вопрос о количестве файлов, загружаемых на странице, и их содержимом.
При использовании подхода AzaBEM на странице достаточно подключать всего 2 файла стилей:
- Файл с общими для всего сайта классами. Там будут: классы разметки, элементы интерфейса, виджеты и пр. Все эти классы имеют свои префиксы;
-
Файл с локальными классами для данной страницы. Здесь собраны все стили, которые нужны лишь на данной странице (или в группе однородных страниц). Такие классы, как упоминалось выше, имеют префикс
p-
.
Иногда для крупных проектов будет необходимо разделить первый файл на несколько. Например, в соответствии с количеством разделов сайта (раздел аудиозаписей, личных сообщений и пр.). Тогда на странице будут загружаться 3 файла:
- Файл с базовыми стилями для всего сайта;
- Файл с базовыми стилями для данного раздела;
- Файл с локальными стилями для данной страницы.
Покажу несколько примеров подключаемых файлов для разных страниц гипотетического сайта:
- Главная страница сайта:
common.css
+main-page.css
. - Форум:
common.css
+forum.css
. - Поиск по форуму имеет много форм, которые используются только на
странице поиска, поэтому мы отделили эти стили от основных стилей форума:
common.css
+forum.css
+forum-search.css
.
В файлахcommon.cssиforum.cssбудет довольно много стилей с префиксомe-. Если вы опасаетесь, что названия этих стилей могут пересечься, введите дополнительный префикс, напрмер,s-(section), и используйте его в файлеforum.cssвместо стандартногоe-.
Вложенные элементы и блоки
Иногда классам предпочитают давать такие названия, которые позволят проследить всё HTML-дерево, в котором находится элемент.
К примеру:
.menu{}
, .menu-list{}
, .menu-list-item{}
, .menu-list-item-link{}
.
Я против такого разврата, и предпочитаю указывать лишь имя конечного элемента:
.menu{}
, .menu-list{}
, .menu-item{}
, .menu-link{}
.
Более того, в данном примере я считаю избыточностью указание класса menu-link
для каждой ссылки, и в CSS вполне допускаю такие записи: .tagsList-item > a
или .tagsList-item_active > a
. При наличии множества пунктов меню, это может заметно сократить вес страницы.
В итоге DOM может выглядеть следующим образом:
В данной структуре в блок l-menu
вложен блок e-search
. Последнему также добавлен класс l-menu-search
для того, что бы указать дополнительные стили данной форме поиска. Если бы эта форма в дополнительных стилях не нуждалась, то класс l-menu-search
можно было бы не прописывать.
Вообще, добавление форме поиска «стороннего» классаl-menu-search— это не очень хорошая практика. Гораздо лучше было бы воспользоваться модификатором.
К примеру, если форма поиска, находящаяся в меню, должна отображаться слега уменьшенной, то можно просто сделать модификатор (e-search_smallили дажеe-search_mod1) и пользоваться им.
Если же поиск в меню заметно отличается от обычного, то разумно было бы и вовсе сделать отдельный классe-searchSmall. Это позволит работать с двумя данными блоками совершенно независимо.
Третьему пункту меню указан класс g-bold
, который делает этот пункт жирным.
В качестве примера форме также добавлены классы с префиксом j-
. Как видите, жёстких требований по именованию данных
j-классов у меня нет. Обычно я просто указываю любое понятное имя.
Также, ради лучшего восприятия кода, не забывайте соблюдать порядок указания классов. Так, модифицирующий класс
l-menu-item_active
должен идти после базового класса l-menu-item
, а e-search-btn
— после e-btn
.
Резюме
Скорее всего, это не последняя версия данной методологии, и в будущем она продолжит своё развитие. Более того, тому, кто будет её использовать, рекомендую самому эксперементировать с ней и модифицировать её под свои нужды. Тем не менее, лично меня она всем устраивает, так как имеет очень важные преимущества перед другими подходами:
- Отличная читабельность выходного кода;
- Отсутствие необходимости подбирать сложные уникальные названия блоков за счёт удобного использования префиксов;
- Несвязанность представления с логикой: мы можем менять вёрстку, не боясь, что после этого «отвалится» весь JS;
- Минимальный объём кода при максимальной гибкости;
- Загрузка не более трёх CSS-файлов на одной странице.