Методология верстки бэм

БЭМ для начинающих. Очевидные и неочевидные вопросы верстки

БЭМ. От теории к практике

  • Основы БЭМ
  • Модификаторы и миксы
  • Удобство параллельной разработки и верстка блоками
  • Блоки в файловой структуре
  • Шаблоны в БЭМ
  • Тестирование верстки
  • Сборка проекта
  • С чего начать?

Основы БЭМ

Методология БЭМ — это набор универсальных правил, которые можно применять независимо от используемых технологий, будь то CSS, Sass, HTML, JavaScript или React.

БЭМ помогает решить следующие задачи:

  • повторно использовать верстку
  • безболезненно менять верстку местами в одном проекте
  • переносить готовую верстку из проекта в проект
  • создавать стабильный, предсказуемый и понятный код
  • сократить время на отладку проекта

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

Именно блокам и элементам посвящены первые две буквы в аббревиатуре БЭМ.

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

Чтобы показать всю силу БЭМ-нейминга, рассмотрим пример с формой. По БЭМ-методологии форма будет представлена блоком form. В HTML имя блока всегда записывается в атрибуте class:

<form class=»form» action=»/»>

Все части формы (блок form), которые не имеют смысла в отрыве от нее, считаются ее элементами. Таким образом поле ввода (search) и кнопка (submit) — это элементы блока form. Принадлежность элемента к блоку также выражается в имени через классы:

<form class=»form» action=»/»>
<input class=»form__search» name=»s»>
<input class=»form__submit» type=»submit»>
</form>

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

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

.form__search {}
.form__submit {}

Запись имен в классах с помощью блоков и элементов решает еще одну важную проблему: избавляет от вложенности селекторов. У всех селекторов в БЭМ-проекте одинаковый вес. То есть переопределять стили, написанные по БЭМ, гораздо удобнее.

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

Суть именования компонентов в БЭМ в том, что в имени можно явно указать связь блока и его элементов.

Третья буква в аббревиатуре БЭМ

Официально буква «М» означает «модификатор», но негласно под нее попадает еще одно очень важное понятие в БЭМ — »микс». И модификаторы и миксы изменяют блок и его элементы. Давайте рассмотрим подробнее.

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

Модификатор определяет внешний вид, состояние и поведение блока либо элемента.

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

Разберемся, как работают модификаторы.

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

header .form {}
footer .form {}

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

В БЭМ можно добавить блоку новые стили с помощью модификатора:

<!— Добавили модификатор form_type_original —>
<form class=»form form_type_original» action=»/»>
<input class=»form__search» name=»s»>
<input class=»form__submit» type=»submit»>
</form>

Запись <form class=»form form_type_original»></form> говорит о том, что блоку form присвоен модификатор type со значением original. В классической схеме имя модификатора отделяется от имени блока или элемента одним подчеркиванием.

Форма может быть уникальна по разным показателям — цвету, размеру, типу, теме оформления. Все эти параметры можно задать с помощью модификатора:

<form class=»form form_type_original form_size_m form_theme_forest»></form>

Одна и та же форма может выглядеть по-разному и при этом быть одного размера:

<form class=»form form_type_original form_size_m form_theme_forest»></form>
<form class=»form form_type_original form_size_m form_theme_sun»></form>

При этом селекторы для каждого модификатора все равно будут иметь одинаковый вес:

.form_type_original {}
.form_size_m {}
.form_theme_forest {} Важно! Модификатор содержит только дополнительные стили, которые как-то изменяют исходную реализацию блока. Это позволяет один раз написать, как должен выглядеть универсальный блок, и добавить в стили модификатора только те свойства, которые отличают блок от его исходного вида. .form {
/* стили для универсального блока */
}
.form_type_original {
/* дополнительные стили */
}

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

<form class=»form form_type_original»></form>

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

Миксы

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

Миксом называется одновременное размещение нескольких БЭМ-сущностей (блоков, элементов, модификаторов) на одном DOM-узле.

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

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

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

.form,
.search,
.register {

}

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

<form class=»form» action=»/»>
<input class=»form__search» name=»s»>
<input class=»form__submit» type=»submit»>
</form>

В селекторе класса .form описаны все стили, которые могут быть у любой формы (заказов, поиска или регистрации):

.form {}

Теперь предстоит сделать из универсальной формы форму поиска. Для этого в проекте необходимо создать дополнительный класс search, который отвечает только за поиск. Чтобы объединить стили и поведение класса .form и .search, нужно разместить эти классы на одном DOM-узле:

<!— Микс блоков form и search —>
<form class=»form search» action=»/»>
<input class=»form__search» name=»s»>
<input class=»form__submit» type=»submit»>
</form>

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

Рассмотрим еще один пример, когда меняется семантика компонента. Для примера возьмем навигационное меню в шапке страницы, в котором все пункты должны быть ссылками:

<nav class=»menu»>
<a class=»link» href=»»></a>
<a class=»link» href=»»></a>
<a class=»link» href=»»></a>
</nav>

Нужная функциональность для ссылок уже реализована в блоке link. Но ссылки в меню должны отличаться визуально от ссылок в тексте. Существует несколько способов изменить ссылки в меню:

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

<nav class=»menu»>
<a class=»menu__item menu__item_link» href=»»></a>
<a class=»menu__item menu__item_link» href=»»></a>
<a class=»menu__item menu__item_link» href=»»></a>
</nav>

В таком случае для реализации модификатора придется скопировать поведение и стили блока link. Это приведет к дублированию кода.

2. Воспользоваться миксом универсального блока link и элемента item блока menu:

<nav class=»menu»>
<a class=»link menu__item» href=»»></a>
<a class=»link menu__item» href=»»></a>
<a class=»link menu__item» href=»»></a>
</nav>

Микс двух БЭМ-сущностей позволит применить базовую функциональность ссылок из блока link и дополнительные CSS-правила из блока menu, и не копировать код.

Внешняя геометрия и позиционирование. Отказываемся от абстрактных HTML-оберток

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

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

Рассмотрим на примере универсального блока меню, который нужно разместить в шапке. В верстке блок меню должен отступать от родительского блока на 20px.

Существует несколько решений для этой задачи:

1. Написать стили с отступами самому блоку меню:

.menu {
margin-left: 20px;
}

В таком случае блок menu перестанет быть универсальным. Если понадобится разместить меню в подвале страницы, придется править стили, потому что отступы скорее всего будут другими.

2. Создать модификатор для блока меню:

<div>
<ul class=»menu menu_type_header»>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
</ul>
</div> .menu_type_header {
margin-left: 20px;
}
.menu_type_footer {
margin-left: 30px;
}

В таком случае в проекте появятся два типа меню, хотя это не так. Меню остается одно и то же.

3. Определить внешнее позиционирование блока — вложить блок menu в абстрактную обертку (например, блок wrap), где задать все отступы:

<div class=»wrap»>
<ul class=»menu»>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
</ul>
</div>

Чтобы окончательно отказаться от соблазна создавать модификаторы и менять стили самого блока для позиционирования его на странице, нужно понять простую вещь:

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

4. Использовать микс. Знания про позиционирование вложенных блоков описываются в элементах родительского блока. Затем элемент родительского блока миксуется к вложенному блоку. В таком случае вложенный блок не специфицирует никакие отступы и может быть легко переиспользован в любом месте.

Продолжим рассматривать пример:

<div>
<ul class=»menu header__menu»>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
<li class=»menu__item»><a href=»»></a></li>
</ul>
</div>

Здесь внешняя геометрия и позиционирование блока menu задана через элемент header__menu. Блок menu не специфицирует никакие отступы и может быть легко использован повторно.

Элемент родительского блока (в нашем случае это header__menu) полностью решает задачу абстрактных блоков-оберток, отвечающих за внешнее позиционирование блока.

Удобство параллельной разработки

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

В качестве примера рассмотрим библиотеку блоков bem-components, которая содержит универсальные блоки, такие как , кнопка, поле ввода. Из универсальных компонентов легко создавать более сложные блоки. Например, селект или чекбокс.

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

Блоки в файловой структуре

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

Реализация каждого блока хранится в отдельной папке проекта. Каждой технологии (CSS, JavaScript, тестам, шаблонам, документации, картинкам) соответствует отдельный файл.

Например, если внешний вид блока input задан с помощью CSS, то код будет сохранен в файле input.css.

project
common.blocks/
input/
input.css # Реализация блока input в технологии CSS
input.js # Реализация блока input в технологии JavaScript

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

project
common.blocks/
input/
input.css # Реализация блока input в технологии CSS
input.js # Реализация блока input в технологии JavaScript
input_theme_sun.css # Реализация модификатора input_theme_sun
input__clear.css # Реализация элемента input__clear в технологии CSS
input__clear.js # Реализация элемента input__clear в технологии JavaScript

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

Файловая структура любого БЭМ-проекта состоит из уровней переопределения. Уровни переопределения позволяют:

  • Разделять проект на платформы
  • Легко обновлять библиотеки блоков, подключенные в проект
  • Использовать общие блоки для разработки разных проектов
  • Менять темы оформления, не затрагивая логику работы проекта
  • Проводить эксперименты в рабочем проекте

Подробнее про уровни переопределения.

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

Шаблонизация в БЭМ

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

Шаблоны позволяют:

  • Сократить время на отладку проекта, так как изменения в шаблоне автоматически применятся ко всем блокам проекта.
  • Изменять разметку блока.
  • Переносить блоки с текущей разметкой в другой проект.

В БЭМ используется шаблонизатор bem-xjst, который содержит два движка:

  • BEMHTML — преобразует BEMJSON-описание страницы в HTML. Шаблоны описываются в файлах с расширением .bemhtml.js.
  • BEMTREE —преобразует данные в BEMJSON. Шаблоны описываются в BEMJSON-формате в файлах с расширением .bemtree.js.

Подробнее о BEMJSON-формате входных данных.

Если шаблоны к блокам не написаны, шаблонизатор по умолчанию установит блокам тег <div>.

Сравните декларацию блоков и выходной результат HTML:

Декларация:

{
block: ‘menu’,
content:
} <div class=»menu»>
<div class=»menu__item»>
<div class=»link»></div>
</div>
<div class=»menu__item menu__item_current»>
<div class=»link»></div>
</div>
</div>

Чтобы изменить разметку блока menu, необходимо написать шаблоны для блока:

1. Меняем тег блока menu:

block(‘menu’)(
tag()(‘menu’) // Устанавливаем тег menu для блока меню
)

Измененный HTML:

<menu class=»menu»> <!— Заменяем тег div на menu для блока menu —>
<div class=»menu__item»>
<div class=»link»></div>
</div>
<div class=»menu__item menu__item_current»>
<div class=»link»></div>
</div>
</menu>

По аналогии с CSS, шаблон будет применен ко всем блокам menu на странице.

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

Шаблоны в БЭМ написаны на JavaScript, поэтому добавить новый элемент в шаблон также можно с помощью JavaScript:

block(‘menu’)(
tag()(‘menu’),
content()(function() {
return {
elem: ‘inner’,
content: this.ctx.content
};
})
) <menu class=»menu»> <!— Заменяем тег div на menu для блока menu —>
<div class=»menu__inner»>
<div class=»menu__item»>
<div class=»link»></div>
</div>
<div class=»menu__item menu__item_current»>
<div class=»link»></div>
</div>
</div>
</menu>

3. Изменяем теги всем элементам inner и item:

block(‘menu’)(
tag()(‘menu’),
content()(function() {
return {
elem: ‘inner’,
content: this.ctx.content
}
}),
elem(‘inner’)(
tag()(‘ul’)
),
elem(‘item’)(
tag()(‘li’)
)
) <menu class=»menu»>
<ul class=»menu__inner»>
<li class=»menu__item»>
<div class=»link»></div>
</li>
<li class=»menu__item menu__item_current»>
<div class=»link»></div>
</li>
</ul>
</menu>

4. Выставляем тег <a> всем ссылкам на странице:

block(‘menu’)(
tag()(‘menu’),
content()(function() {
return {
elem: ‘inner’,
content: this.ctx.content
}
}),
elem(‘inner’)(
tag()(‘ul’)
),
elem(‘item’)(
tag()(‘li’)
)
);
block(‘link’)(
tag()(‘a’)
); <menu class=»menu»>
<ul class=»menu__inner»>
<li class=»menu__item»>
<a class=»link»></a>
</li>
<li class=»menu__item menu__item_current»>
<a class=»link»></a>
</li>
</ul>
</menu>

5. Изменяем существующий шаблон. Правила в шаблонах применяются так же, как в CSS: нижнее правило перекрывает верхнее. Добавим новые правила в шаблон, изменим тег ссылкам с <a> на <span>:

block(‘link’)(
tag()(‘a’)
);
block(‘link’)(
tag()(‘span’)
); <menu class=»menu»>
<ul class=»menu__inner»>
<li class=»menu__item»>
<span class=»link»></span>
</li>
<li class=»menu__item menu__item_current»>
<span class=»link»></span>
</li>
</ul>
</menu>

Тестирование верстки

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

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

Сборка проекта

Для удобства работы с кодом в БЭМ-проекте все блоки и технологии разложены по отдельным папкам и файлам. Чтобы объединить исходные файлы в один (например, все CSS-файлы в project.css, все JS-файлы в project.js и т. п.), используется сборка.

Сборка решает следующие задачи:

  • Объединяет исходные файлы, разложенные по файловой структуре проекта.
  • Подключает в проект только необходимые блоки, элементы и модификаторы (БЭМ-сущности).
  • Учитывает порядок подключения.
  • Обрабатывает код исходных файлов в процессе сборки (например, компилирует LESS-код в CSS-код).

Чтобы включить в сборку только необходимые БЭМ-сущности, необходимо составить список блоков, элементов и модификаторов, используемых на странице. Такой список называется декларацией.

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

С чего начать?

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *