Как правильно работать с БЭМ-методологией

Ключевые слова и тезисы

Методология, БЭМ, документация, примеси, правильные миксы по БЭМ

3 июля 2020

Дата публикации\правок.

Документация

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

Поделись

Если считаешь материал полезным и он тебе помог. Если же у тебя остались вопросы, пиши mdash; info@i-am.studio.

Что такое БЭМ

В данной «документации» я хочу разложить насколько возможно по полочкам принципы и подходы к методологии БЭМ. Искренне надеюсь, что подойдет, как бывалому, так и новичку въехать в технологию.

Итак, БЭМ расшифровывается очень просто:

  • Блок;
  • Элемент;
  • Модификатор.

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

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

Модификатор — волшебная палочка как для блока, так и для элемента. Многие ошибочно полагают, что модификатор позволяет дать возможность блоку как-то узнать о происходящем снаружи, например задать margin, но нет, это [задавание отступов модификаторами] работает только для элементов внутри блока.

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

Простейшим примером классического блока может быть логотип на сайте:

  • Есть сам логотип, он же блок, т.е. родитель.
  • Внутри есть элемент картинка.
  • А может быть и текстовая подпись.
  • Логотип может быть адаптивным, т.е. будет изменяться картинка, положение текста в зависимости от разрешения экрана. Или от текущего класса модификатора.

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

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


Как разрабатывать по БЭМ-методологии

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

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

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

Что же надо сделать — разработать структуру блоков в проекте. По-отдельности и потом можно собирать вместе, словно конструктор.

Структурная часть

Возьмем за образец какой-нибудь типовой воображаемый прототип сайта. В нем будет:

Набор типовых блоков для проекта

Вот, что примерно должен разглядеть верстальщик в макете. И дальше остается руководствоваться логикой, например:

— Логотип в подвале и шапке выполняет роль ссылки, стоит ли вкладывать его в блок ссылки?
 — Вряд ли, это вполне самостоятельный блок.

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

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

Для того, чтобы скрыть элемент в «мобильной версии» логотипа вовсе не обязательно ему указывать display:none, можно просто не добавлять его в верстку\рендерить (на уровне JS).

Таким образом легко решается проблема переопределения стилей. Ведь если они не заданы, то и переопределять нечего и нет никакого смысла это делать.

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

Модификаторы

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

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

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

Как называть классы БЭМ

Основной критерий, который очень важен — читаемость. Когда ты смотришь на HTML-код, то должен понимать, что будет происходить с содержимым, т.е. самой версткой.

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

  • Размер можно указывать, как --2x, а не --double-size.
  • Цвет color можно опускать и задавать --white. Правда в случае с фоном придется добавить --background-white модификатор.

Стили\схемы наименований

Есть несколько схем наименований по БЭМ, я прошелся по нескольких и в конечном счете перешел на международный стиль. Когда элемент отделяется от блока двумя нижними подчеркиваниями, а модификатор двумя дефисами. Это выглядит вполне читаемо: block__element--modifier.

Миксы по БЭМ

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

Далеко ходить не надо, обратимся к структуре выше и вспомним про шапку и подвал. Шапка и подвал — определенно блоки. А вот как в них правильно вложить логотипа и навигацию? Напоминаю, что блок не знает своего положения в мире, он описывает только внутрь. Поэтому отступы в шапке для логотипа определяет шапка. Как?

Все просто, у нас есть блока шапка, скажем .header (воу конкретные примеры пошли). И внутри нее есть элемент .header_logo — что этот элемент делает. Ну во-первых он миксуется с недавно созданным логотипом. Должно получится что-то в духе:

Пример микса шапки и логотипа

Итак, на втором уровне вложенности у нас появился микс. header__logo миксуется с logo. Зачем?

Все просто, лого, как я уже не раз говорил, не знает своего положения в обществе, оно описывает то, как должно работать лого и только. А вот элемент header__logo уже знает, как должно вести себя лого внутри header. Опасный момент, знает только как вести себя внутри header! Это важно.

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

Тут-то осталась одна неразгаданная тайна, как header поймает свое положение на странице, если оно вроде как ничего не знает о том, что вокруг?

Все просто, в документе должен появится блок страниц, например .page. Он станет корневым и с помощью элемента .page__header и микса с шапкой расположит ее в ее законном месте. Может быть, да? Не совсем, ведь есть еще модификаторы, которые позволяют в произвольном порядке модифицировать как блоки, так и элементы. Но как это применимо в данном случае?

Вот несколько простых примеров:

  • Обычный page содержит в себе статическую шапку и вроде бы как ничего не описывает для нее. Допустим только отступ вниз, чтобы основной контент не был прибит прямо к шапке.
  • Или вот у нас идет heroscreen весь из себя такой красивый и надо при прокрутке, чтобы шапка вставала сверху и крутилась с пользователем. Тут хватит добавить position:sticky шапке, а сделать это можно через модификатор элемента page__header--sticky.
Пример микса шапки и логотипа + модификатор

Сам page скорее всего не будет иметь модификаторов, хотя иной раз надо задавать, например темную тему для сайта, а это можно как раз сделать одним махом для всей основной структуры задав модификатор page--dark. Хороший ли это подход? На самом деле не очень, так как все равно надо задавать подобный модификатор вложенным блокам, чтобы корректно их отрисовать. Переопределять цвет логотипа (svg например со свойством fill) через наследование в несколько миксов — плохая идея, специфичность не вывезет. Оптимальное спустить команду вниз, линейно, как в дереве и заложить это на уровне JS. Можно хоть mutationObserver прикрутить, если совсем лень-матушка.


Конкретные примеры по БЭМ

1. Блок БЭМ с модификаторами, но без элементов

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

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

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

2. Как сочетается с атомарным подходом

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

Атомарный подход пропагандирует класс для всего. Точнее классы для всего, на каждое свойство и значение CSS будет свой класс, нужен цвет бирюзовый — будет класс .color--turquoise. Нужен отступ сверху в 23 пиксела — будет класс margin-top--23px.

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

3. Создавать обертку (wrapper) или нет?

Классика, быть или не быть, создавать ли для news класс-обертку news-wrapper или нет?

Конечно же нет, нормальный подход наименования классов гласит — названия классов должны отвечать на вопрос, что он делает. А wrapper, обертка, что?

Рассмотрим частый кейс — есть карточка новостей. Как описать поведение группы карточек?

Допустим для класса карточки новостей мы создали класс news-card. Внутри будут элементы, но они нас не волнуют. По первому пути, тривиальному, у нас будет просто список карточек. Его спокойно можно обозвать news-cards-list. И модификаторами можно задавать разное количество карточек в строке.

<main class="page page--news">
    <div class="news-list">
        <ol class="news-list__ordering list">
            <li class="list__item list__item--offset-left"><a href="#" class="link">Как придется</a></li>
            <li class="list__item list__item--offset-left"><a href="#" class="link">По порядку</a></li>
            <li class="list__item list__item--offset-left"><a href="#" class="link">На отъебись</a></li>
            <li class="list__item list__item--offset-left"><a href="#" class="link">Жри как дают</a></li>
        </ol>
        <div class="news-list__cards-list">
            <div class="news-card"></div>
            <div class="news-card"></div>
            <div class="news-card"></div>
            <div class="news-card"></div>
            <div class="news-card"></div>
        </div>
    </div>
</main>
        

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

  • Создать блок news, внутри которого будет news__nav и news__list, а еще можно протянуть и news__item.news-card в такой вот микс, да.
  • Создать блок news, все как выше, но не вкладывать напрямую, а сделать блок-прослойку — news-list, у которого будут элементы news-list__item.news-card.

И второй вариант будет предпочтительнее, так как позволит не уходить слишком глубоко (фактически в HTML) в плоской структуре стилей. Но в конечном счете все зависит от макета. Так как news-card может быть использован и вне списка новостей. Может быть это будет отдельно стоящая карточка с новостью в тексте статьи. Помним, блок описывает только то, что знает наверняка — себя и свое содержимое, поэтому с легкостью может быть встроен почти куда угодно, ведь внешние положение за него опишет элемент родительского блока.

4. Классу active быть или нет

Я часто повторяю, что при все своей красоте БЭМ — это вовсе не панацея и уж точно не свод жестких правил. Поэтому да, для каких-то случаев, где есть скрипт, может быть уже готовый, или просто очень лень писать всю, или может нужно написать один для всех элементов, вполне можно написать модификтор без полной записи. Т.е. например tab, на который нажали будет не tab.tab--active, а tab.active.

Хотя в конечном счете при адекватной сборке хватит обычной реактивности и триггера, которые должен отдавать true или false значение, чтобы активировать класс элемента, который был заранее указан. При этом наименование этой переменной в JS или ЯП бэкенда значение не имеет. В рамках разумного, конечно, читаемые названия это важно.


Файловая структура по БЭМ

То, что можно найти в документации на этот счет — вполне недурственный подход, который на самом деле в реальной жизни не очень подходит.

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

Хотя пакетные сборщики, конечно, могут спокойно и обойти собрать, что надо.

Как хранить файлы

У нас есть в среднем 3 типа файлов:

  • Стили;
  • JavaScript;
  • HTML.

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

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

Поэтому в большинстве случаев скорее всего в проекте не будет отдельно-стоящего HTML-файлика, разве что в качестве образца. В jinja2 своя область видимости и совать в нее CSS и JavaScript не имеет никакого смысла. В проекте без шаблонизатора на PHP скорее всего у тебя будут свои какие-то уловки для генерации блока. К слову, миксовать на уровне бэкенда не так уж и просто.

Итого — да, хранить файлы отдельно каждые в своей папке с названием блока — удобно. Не менее удобно и заводить отдельную структуру для Sass-файлов, где все блоки будут лежать непосредственно в папке sass/blocks/, а js в js/blocks/.

Конкретные примеры исключений

Моей первой попыткой объединить все файлы в одной структуре работали в FA-Kit-е на базе языка pug. Но это даже не вышло в какой-либо релиз. Удобно собирать только темы для Themeforest. Статические и без ничего.

Апогеем же разработки стали для меня однофайловые компоненты Vue, где в одном .vue файле сразу html, js и стилей (притом без разницы на каком препроцессоре написанные). На самом деле там и на pug можно структуру оформлять.

А props-ы и расчетные data значения позволяют рендерить отдельные элементы или нет.

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

  • Blocks;
  • Pages;
  • Sections;
  • Layouts.

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

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

Это все позволяет держать в относительном порядке структуру проекта.


Идеология БЭМ

На самом деле данная идеология не заканчивается на уровне классов. Навыки можно спокойно применять и дальше, формируя нормальные человекочитаемые переменные в JS или ЯП бэкенда, за которые не хочется надрать задницу автору переменных типа lkCp, aPP и т.д.

Но можно пойти еще дальше и структурировать директории примерно подобным образом. И называть файлы типа footer-wave--small.svg и footer-wave--big.svg. Тут сам БЭМ уже сильно с натяжкой, но ты должен понимать, к чему я клоню.