EN RU
Форум

Методология

Технологии

Инструментарий

Библиотеки

Учебные материалы

Решение распространенных проблем веб-разработки с помощью БЭМ

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

Как упростить код и облегчить рефакторинг

Проблема

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

Когда страницу верстает один разработчик в короткие сроки, коллизии имен можно избежать. Но если над проектом работают несколько человек или правки нужно внести спустя какое-то время, то отследить зависимые имена компонентов становится сложно. В больших проектах результатом правки одного класса может стать десяток «разъехавшихся» страниц.

Например, для создания навигационного меню могут использоваться следующие имена классов:

<ul class="nav">
    <li class="item active"><a class="link">One</a></li>
    <li class="item"><a class="link">Two</a></li>
    <li class="item"><a class="link">Three</a></li>
</ul>

К ним могут быть написаны CSS-правила:

.item
{
    padding: 4px 10px;
    color: black;
}

.active
{
    font-weight: bold;
    background: #ffc7c7;
}

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

Или предположим, что в навигационном меню нужно изменить правила класса .active. По имени непонятно, какие компоненты его используют. Может оказаться, что на другой странице существует, например, кнопка <div class="button active">Нажми меня!</div>. Тогда изменение правил для .active повлияет на стили этой кнопки.

Чтобы разобраться, можно ли безболезненно изменить стили для класса .active, разработчику придется просмотреть всю структуру страницы или проекта. Любое изменение потребует значительных временных затрат только на поиск зависимых компонентов.

Решение

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

Применение правил по именованию позволяет:

Рассмотрим тот же пример навигационного меню:

<ul class="nav">
    <li class="item active"><a class="link">One</a></li>
    <li class="item"><a class="link">Two</a></li>
    <li class="item"><a class="link">Three</a></li>
</ul>

Но применим к нему правила именования БЭМ: класс nav будет обозначать имя блока, nav__item и nav__link — имена элементов, а nav__item_active — имя модификатора элемента item.

В таком случае запись будет следующей:

<ul class="nav">
    <li class="nav__item nav__item_active"><a class="nav__link">One</a></li>
    <li class="nav__item"><a class="nav__link">Two</a></li>
    <li class="nav__item"><a class="nav__link">Three</a></li>
</ul>

И, соответственно, CSS будет иметь такой вид:

.nav__item
{
    padding: 4px 10px;
    color: black;
}

.nav__item_active
{
    font-weight: bold;
    background: #ffc7c7;
}

Новые имена CSS-классов содержат всю информацию о структуре блока. А это значит, что больше не нужно просматривать HTML-код страницы, чтобы определить все зависимости. Cелектор всегда содержит знания о том, на какой блок или элемент влияют его правила (в данном случае на элемент nav__item). Разработчику не придется думать о возможном существовании кнопки <div class="button active">Нажми меня!</div>, так как еe CSS-правила будут записаны как .button_active и не будут зависеть от правил модификатора active для пункта меню (nav__item_active).

Использование длинных имен имеет следующие недостатки:

  • Результирующий код весит больше. Эта проблема решается gzip, который сжимает повторяющиеся последовательности в именах.

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

Как получить самодокументируемый код

Проблема

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

Решение

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

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

Рассмотрим пример с формой поиска на сайте. Не будем обращаться к HTML, попытаемся прочитать только CSS и понять, какую часть интерфейса он описывает.

Вариант реализации формы в классической верстке:

form {}

input
{
    background: red;
}

input[type=submit]
{
    background: buttonface
}

Такой способ записи не отражает связи между:

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

Напишем CSS на классы:

.form {}
.field {}
.submit {}

Код стал информативнее: теперь понятно, что есть форма, поле и какой-то компонент submit. Но такие имена все еще не дают понять, относится ли поле (field) к форме (form), или что произойдет, если полей или форм на странице будет несколько. Снова возникает необходимость обращаться к HTML.

Перепишем пример, используя соглашение по именованию БЭМ:

.form {}
.form_search {}
.form__field {}
.form__submit-button {}

Такая запись дает понять, как работает данный код. Имена CSS-классов показывают, что:

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

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

Как начать повторно использовать код и избежать взаимного влияния компонентов друг на друга

Проблема

Разработчик использует схожий набор компонентов при разработке страниц одного проекта. Например, на странице может быть несколько типов блока menu.

Рассмотрим проблему на примере навигационного меню:

<ul class="nav">
    <li class="item"><a class="link">One</a></li>
    <li class="item"><a class="link">Two</a></li>
    <li class="item"><a class="link">Three</a></li>
</ul>

CSS-стили к пункту item могут быть записаны как:

.item
{
    padding: 4px 10px;
    color: black;
}

Если на страницу понадобится добавить дополнительные компоненты, содержащие пункты, то появится еще один блок кода с классом item, например:

<div class="snippets">
    <div class="item">
        <h2 class="title"></h2>
        <img class="thumb">
    </div>
</div>

В этом случае CSS может быть оформлен с помощью каскадов. Для этого достаточно доопределить правила, уже написанные для .item:

.item
{
    padding: 4px 10px;
    color: black;
}

.snippets .item
{
    color: red;
    font-size: 14px;
}

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

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

Решение

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

Запишем тот же код в соответствии с правилами именования БЭМ:

<ul class="nav">
    <li class="nav__item"><a class="nav__link">One</a></li>
    <li class="nav__item"><a class="nav__link">Two</a></li>
    <li class="nav__item"><a class="nav__link">Three</a></li>
</ul>
.nav__item
{
    padding: 4px 10px;
    color: black;
}

В таком случае добавление нового пункта item на страницу будет выглядеть так:

<div class="snippets">
    <div class="snippets__item">
        <h2 class="snippets__title"></h2>
        <img class="snippets__thumb">
    </div>
</div>

Пункт snippets__item будет иметь соответствующие только ему уникальные CSS-правила:

.snippets__item
{
    padding: 4px 10px;
    color: red;
    font-size: 14px;
}

Изменения в nav__item не влияют на snippets__item, так как пункты получают уникальные имена благодаря пространству имен, заданному именем блока. Это позволяет формировать независимые CSS-правила для всех элементов блока.

Такой подход дает возможность защитить элементы от взаимного влияния друг на друга — элементы всегда являются частью блока. Такой же принцип работы использует и Shadow DOM в Web Components. Но, в отличие от Shadow DOM, применение соглашения по именованию БЭМ не зависит от совместимости с работой браузеров.

Блоки snippets и nav можно повторно использовать и перемещать по странице или проекту. Уникальность имен классов, основанная на правилах именования БЭМ, позволяет блокам не зависеть друг от друга.

Использование каскадов в БЭМ

Методология БЭМ допускает использование каскадов.

Например, каскад уместен, чтобы менять элементы в зависимости от состояния блока или заданной ему темы:

.nav_hovered .nav__link
{
    text-decoration: underline;
}
.nav_theme_islands .nav__item
{
    line-height: 1.5;
}

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

Как разместить несколько сущностей на одном DOM-узле и избежать «Copy-Paste»

Проблема

При работе с проектами может потребоваться повторно использовать реализованную функциональность.

Во многих случаях такую проблему решают копированием нужной части кода в новый компонент. Такой подход имеет следующие недостатки:

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

Решение

Воспользуемся примером, который реализует универсальный блок навигационного меню и написан по всем правилам именования БЭМ.

<ul class="nav">
    <li class="nav__item"><a class="nav__link">One</a></li>
    <li class="nav__item"><a class="nav__link">Two</a></li>
    <li class="nav__item"><a class="nav__link">Three</a></li>
</ul>

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

Допустим, в разделе новостей уже есть блок articles, которому написаны все необходимые CSS-правила.

Смешать реализации двух разных блоков без копирования кода можно при помощи микса. То есть разместить на одном DOM-узле блок nav и элемент articles__nav.

В коде это будет выглядеть так:

<ul class="nav articles__nav">
    <li class="nav__item"><a class="nav__link">One</a></li>
    <li class="nav__item"><a class="nav__link">Two</a></li>
    <li class="nav__item"><a class="nav__link">Three</a></li>
</ul>

Такая реализация позволит объединить функциональность блока nav и особенности реализации элемента articles__nav (внешний вид новостных статей в меню). При этом нет необходимости копировать уже имеющиеся CSS-правила. При обнаружении ошибки, правки необходимо будет внести только в одну часть кода.

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