БЭМ-методология организации CSS-кода

Писать CSS-код легко. Масшабировать и поддерживать его — нет

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

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

Почему так происходит? На начальном этапе, когда разработчики получают макеты, они создают структурy ассетов, в которую довольно хорошо вписывается весь существующий дизайн. Проблема рождается именно в этот момент, потому что текущий дизайн — это вершина айсберга по сравнению с будущим дизайном. Можно представить варианты решений, приходящих в голову на данном этапе. У нас всего две страницы — можно поместить все стили в один файл. Или еще. У нас десять страниц и все они непохожи, поэтому не стоит заморачиваться выделением компонент внутри страниц и вынесением их в отдельные файлы, проще создать по файлу стилей на каждую страницу. Идеи выглядят вполне здравыми, учитывая текущие аспекты дизайна. Опытные разработчики даже могут сделать некоторые предположения о том, как поменяется дизайн в ближайшем будущем, но немногие готовы к тому, во что превратится проект через, скажем, пять лет.

CSS имеет ряд недостатков, которые приводят к вышеперечисленным проблемам. В недавнем докладе (React: CSS in JS), породившем множество дискуссий в среде фронтенд разработчиков, один из сотрудников Facebook озвучил проблемы с масштабированием CSS. Среди них использование глобального пространства имен, удаление мертвого кода, изоляция и т. д. В итоге он предложил хранить стили в JavaScript. Интересное, но довольно радикальное решение, не всегда применимое к обычным сайтам, страницы которых рендерятся на сервере. Многие компании, не только Facebook, пытаются решить проблему масштабирования CSS. Поэтому на сегодняшний день существует множество подходов к написанию стилей. Одна из наиболее интересных методологий родилась в Yandex.

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

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

Теперь можно вернуться к нашим проблемам и посмотреть какое решение нам предлагает БЭМ. Первая проблема — это глобальное пространство имен. Пусть у нас есть навигационное меню.

<ul class="toolbar">
  <li class="item edit">
    <a href="/edit">Edit</a>
  </li>
  <li class="item delete">
    <a href="/delete">Delete</a>
  </li>
</ul>

И стили к нему

.item {
  display: inline-block;
  padding: 5px 10px;
  margin: 0 10px;
}

.edit {
  padding-left: 20px;
  background: url('edit-icon.png');
}

Если вдруг на странице появится другой компонент, содержащий элемент списка (item), или некий элемент связанный с редактирванием (edit), то новые стили повлияют на уже существующие.

<div class="user-profile">
  <span class="email">john.doe@example.com</span>
  <a class="edit">Change email</a>
</div>
.edit {
  background: url('edit-email-icon.png');
}

В этом примере проблема решается выделением двух компонент. toolbar и user-profile — имена этих компонент в глобальном пространстве имен. Дальше мы определяем стили внутренних элементов в пределах этих компонент.

.toolbar .edit {
  ...
}

.toolbar .item {
  ...
}

.user-profile .edit {
 ...
}

Но такое решение очень плохо масштабируется. Если представить себе более сложные компоненты (page, company-preview, user-post), то мы получим ту же самую проблему, но уже в пределах одного компонента. Уточнение селекторов, например .page > .edit — очень плохая идея, так как создает связанность между HTML шаблонами и представлением. Меняя шаблон, нам нужно будет поменять и стили тоже. Вдобавок ко всему добавляя класс к селектору мы меняем его специфичность, а это в свою очередь усложняет переопределение CSS правил для элемента. В других местах появляются классы для увеличения специфичности, начинается гонка селекторов и головная боль для верстальщика.

БЭМ предлагает отказаться от каскадности, тем самым однозначно определяя элемент.

<ul class="toolbar">
  <li class="toolbar__item toolbar__item_edit">
    <a href="/edit">Edit</a>
  </li>
  <li class="toolbar__item toolbar__item_delete">
    <a href="/delete">Delete</a>
  </li>
</ul>
.toolbar { ... }
.toolbar__item { ... }
.toolbar__item_edit { ... }
.toolbar__item_delete { ... }

Вложенные элементы, принадлежащие блоку, будут использовать это имя блока toolbar в качестве префикса. __ служит разделителем между блоком и элементом, а _ — разделителем между БЭМ-сущностью (в данном случае элементом) и модификатором. Здесь toolbar__item_edit и toolbar__item_edit — это модификаторы элемента toolbar__item.

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

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

  • Позиционирование блока задается родителем
  • Для описания сущностей используются классы, но не id
  • Нельзя создавать элементы элементов (block__elem1__elem2)
  • В именах модификаторов и элементов всегда присутствует имя блока
  • Из предыдущего пункта следует, что нельзя создавать глобальные модификаторы
  • Блоки могут не содержать вложенных элементов (link)
  • Блоки могут заключать в себе все содержимое страницы или крупные ее части (page, page-section)

Чтобы не утратить возможность переносимости блока между разными частями страницы желательно чтобы позиционирование и размеры блока (margin, top, left, width) задавались родителем.

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

<header class="header">
  <div class="header__toolbar">
    <ul class="toolbar">
      <li class="toolbar__item toolbar__item_edit">
        <a href="/edit">Edit</a>
      </li>
      <li class="toolbar__item toolbar__item_delete">
        <a href="/delete">Delete</a>
      </li>
    </ul>
  </div>
</header>
.header { ... }
.header__toolbar {
  float: right;
  width: 50%;
}

CSS код блока toolbar при этом остается неизменным.

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

Разберем еще один пример. Предположим к нам пришел такой дизайн тулбара.

toolbar-design.png

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

<div class="toolbar">
  <div class="toolbar__item toolbar__text">
    android
  </div>
  <a class="toolbar__item toolbar__button"></a>
  <a class="toolbar__item toolbar__button">✓ Mark as read</a>
  <a class="toolbar__item toolbar__button toolbar__button_toggled">∀ View all</a>
  <a class="toolbar__item toolbar__button"></a>
  <a class="toolbar__item toolbar__button"></a>
  <div class="toolbar__item toolbar__spacer"></div>
  <a class="toolbar__item toolbar__button">
    <img class="toolbar__icon" alt="errors" src="/errors.svg" />
  </a>
  <a class="toolbar__item toolbar__button">
    <img class="toolbar__icon" alt="avatar" src="/avatar.jpeg" />
    <span class="toolbar__text">dra1n</span>
  </a>
</div>

Здесь для того чтобы не дублировать общие стили для каждого элемента блока toolbar мы выделяем элемент toolbar__item и используем микс с остальными элементами: toolbar__button, toolbar__spacer и toolbar__text. Некоторые элементы являются вложенными, но при этом внутренняя структура блока все еще остается плоской, то есть не нарушается правило о том, что нельзя создавать элементы элеметов. И еще одно — все элементы, требующие стилизации имеют классы, в том числе и img. Ссылка на реализацию со стилями.

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

Связаться