HTML по БЭМ
В БЭМ HTML-разметку можно создавать вручную или генерировать автоматически. Принципы организации HTML-кода в обоих случаях одинаковы:
Привязка блоков к DOM-узлу
Разметка страницы описывается в терминах блоков, элементов и модификаторов.
Чтобы указать, что блок, элемент или модификатор находятся на DOM-узле, их имена записываются в атрибуте class
.
В простейшем случае одному DOM-узлу соответствует один блок:
<span class="menu"></span>
Несколько блоков на одном DOM-узле
Чтобы совместить стили и поведение нескольких БЭМ-сущностей, необходимо разместить их на одном DOM-узле. Для этого в значении атрибута class
указываются имена БЭМ-сущностей, разделенные пробелом. Такой подход называется миксом.
Микс используется, например, чтобы добавить блоку или элементу модификатор. В примере ниже к стилям блока menu
добавлены новые стили модификатора этого блока menu_theme_bright
:
<span class="menu menu_theme_bright"></span>
Один блок на нескольких DOM-узлах
Для решения JavaScript-задач, например, для одновременной инициализации одного экземпляра блока в разных частях страницы, одну БЭМ-сущность можно разместить на нескольких DOM-узлах.
Пример включает особенности реализации фреймворка i-bem.js. Читать подробнее про i-bem.js
Вложенность элементов
Правила именования запрещают отражать иерархию в названии элемента (block__elem1__elem2
). Но в HTML элементы можно вкладывать друг в друга. Допустима любая вложенность элементов.
В примере ниже пункты меню представлены ссылками. Такая структура блока реализуется за счет вложенности элементов:
<ul class="menu">
<li class="menu__item">
<a class="menu__link" href="https://">...</a>
</li>
</ul>
Использование HTML-оберток
Чтобы расположить один блок относительно другого или позиционировать блоки внутри другого блока в БЭМ принято использовать миксы. Если решить эти задачи с помощью миксов невозможно, применяются HTML-обертки.
Расположение блока относительно других блоков
Чтобы позиционировать один блок относительно другого блока, используется микс.
В примере блоки header
и footer
позиционируются на странице с помощью микса с элементами блока page
, которым заданы нужные стили. Элементы page__header
и page__footer
опциональные и применяются к блоку page
, если необходимо разместить шапку (header
) или подвал (footer
) на странице. Блоки page
, header
и footer
остаются независимыми, так как не содержат стили про взаимное позиционирование.
HTML-реализация:
<body class="page">
<!-- верхний колонтитул и навигация -->
<header class="header page__header">...</header>
<!-- нижний колонтитул -->
<footer class="footer page__footer">...</footer>
</body>
CSS-реализация:
.page__header {
padding: 20px;
}
.page__footer {
padding: 50px;
}
Расположение HTML-элементов внутри блока
Чтобы позиционировать HTML-элементы внутри блока, используется дополнительный элемент этого блока (например, button__inner
). Элемент button__inner
содержит стили про позиционирование внутри блока button
и заменяет абстрактную обертку.
В примере иконка (блок icon
) позиционируется внутри универсальной кнопки с помощью стилей элемента button__inner
.
HTML-реализация:
<button class="button">
<span class="button__inner">
<span class="icon"></span>
</span>
</button>
CSS-реализация:
.button__inner {
margin: auto;
width: 10px;
}
Создание HTML вручную
Чтобы создавать HTML вручную, необходимо следовать правилам, описанным выше.
В HTML разметка блока повторяется каждый раз, когда блок встречается на странице. Если разработчик пишет HTML вручную, исправлять ошибку или вносить дополнительные изменения необходимо в каждом экземпляре блока в разметке. Поэтому в БЭМ-проектах не принято писать HTML руками.
Автоматическая генерация HTML
Чтобы генерировать HTML-код автоматически и иметь возможность внести изменения в реализацию блока в одном файле и применить ко всем экземплярам блока в разметке, в БЭМ используются шаблоны.
Шаблоны — это технология реализации блока, результатом работы которой является HTML этого блока. С помощью шаблонов текущий HTML-элемент может быть подменен на другой или дополнен новыми.
В БЭМ-платформе разработана технология для создания шаблонов — BEMHTML. Все примеры в этом разделе приведены с использованием этого шаблонизатора.
Шаблоны в БЭМ пишутся декларативно. Это позволяет применять к ним основные принципы методологии:
Одни термины во всех технологиях
Шаблоны описываются в терминах блоков, элементов и модификаторов. Поэтому для работы с шаблонами используется дополнительный уровень абстракции над DOM-деревом — БЭМ-дерево. БЭМ-дерево описывает имена БЭМ-сущностей, их состояния, порядок и вложенность. На основании этих данных шаблонизатор строит дерево узлов HTML-разметки блока.
БЭМ-дерево можно выразить любым форматом, который поддерживает древовидную структуру.
Пример DOM-дерева и соответствующего ему БЭМ-дерева:
<header class="header">
<img class="logo">
<form class="search-form">
<input class="input">
<button class="button"></button>
</form>
<ul class="lang-switcher">
<li class="lang-switcher__item">
<a class="lang-switcher__link" href="url">en</a>
</li>
<li class="lang-switcher__item">
<a class="lang-switcher__link" href="url">ru</a>
</li>
</ul>
</header>
БЭМ-дерево:
header
logo
search-form
input
button
lang-switcher
lang-switcher__item
lang-switcher__link
lang-switcher__item
lang-switcher__link
Разделение кода на части
Код шаблонов хранится в отдельных файлах блока в соответствии с принципами организации файловой структуры проекта.
Шаблоны можно писать для всего блока и отдельно для элементов или модификаторов.
Пример файловой структуры блока menu
:
menu/ __item/ menu__item.css menu__item.js menu__item.tmpl # Шаблон элемента menu__item menu.css menu.js menu.tmpl # Шаблон блока menu
Шаблон блока menu
:
block('menu')(
tag()('ul') // Установлен тег <ul> для блока `menu`
);
Шаблон элемента menu__item
:
block('menu').elem('item')(
tag()('li') // Установлен тег <li> для всех элементов `menu__item`
);
HTML-реализация блока menu
, результат работы шаблонов:
<ul class="menu">
<li class="menu__item">...</li>
<li class="menu__item">...</li>
</ul>
Использование уровней переопределения
С помощью уровней переопределения можно:
Переопределение шаблона
В примере рассмотрен шаблон блока menu
из библиотеки, подключенной в проект на отдельный уровень переопределения:
project library.blocks/ # Уровень переопределения с блоками библиотеки menu/ # Блок menu из библиотеки __item/ menu__item.tmpl # Шаблон элемента menu__item menu.css menu.js menu.tmpl # Шаблон блока menu common.blocks/
Шаблоны блока menu
и элемента menu__item
из библиотеки:
// Шаблон блока menu
block('menu')(
tag()('ul'),
attrs()(function() { ... }),
addJs()(true),
addMix()({ ... })
);
// Шаблон элемента menu__item
block('menu').elem('item')(
tag()('li')
);
Блок menu
в проекте представлен таким БЭМ-деревом:
menu
menu__item
menu__item
По БЭМ-дереву шаблонизатор по умолчанию формирует HTML-разметку в виде списка ul + li
:
<ul class="menu">
<li class="menu__item"><li>
<li class="menu__item"><li>
</ul>
Чтобы переопределить шаблон и изменить связку ul + li
на nav + a
, необходимо:
создать файлы шаблонов на уровне проекта;
задать новое значение свойству
tag
.
Файловая структура проекта:
project library.blocks/ # Уровень переопределения с блоками библиотеки menu/ # Блок menu из библиотеки __item/ menu__item.tmpl menu.css menu.js menu.tmpl common.blocks/ # Уровень переопределения с блоками проекта menu/ __item/ menu__item.tmpl # Переопределенный шаблон элемента menu__item menu.tmpl # Переопределенный шаблон блока menu
Шаблоны с уровня common.blocks
:
// Шаблон блока menu
block('menu')(
// Переопределено только значения свойства tag
tag()('nav')
);
// Шаблон элемента menu__item
block('menu').elem('item')(
tag()('a')
);
В результате сборки изменения, внесенные в шаблон, применятся ко всем блокам menu
и элементам menu__item
в проекте:
// Шаблон блока menu с уровня библиотеки
block('menu')(
tag()('ul'),
attrs()(function() { ... }),
addJs()(true),
addMix()({ ... })
);
// Переопределенный шаблон блока menu
block('menu')(
// Переопределено только значение свойства tag
tag()('nav')
);
// Шаблон элемента menu__item с уровня библиотеки
block('menu').elem('item')(
tag()('li')
);
// Переопределенный шаблон элемента menu__item
block('menu').elem('item')(
tag()('a')
);
По аналогии с CSS нижнее правило переопределит верхнее. В результате применения шаблона HTML-код изменится на:
<nav class="menu">
<a class="menu__item" href="...">...</a>
<a class="menu__item" href="...">...</a>
</nav>
Добавление дополнительных HTML-элементов
С помощью шаблонов блоки можно изменять в runtime, например, добавлять новые HTML-элементы.
В проекте блок menu
представлен таким БЭМ-деревом:
menu
menu__item
menu__item
Чтобы позиционировать элементы (menu__item
) внутри блока (menu
), необходимо создать служебный элемент, например, menu__inner
. Новый элемент не относится к данным блока и служит только для добавления разметки. Поэтому его можно добавить в runtime, то есть при рендеринге блока в HTML.
В примере элемент menu__inner
добавлен в шаблон с помощью JavaScript-кода:
block('menu')(
tag()('menu'),
// Добавлен элемент menu__inner
content()(function() {
return {
elem: 'inner',
content: this.ctx.content
};
})
);
// Установлен тег <ul> для элемента menu__inner
elem('inner')(
tag()('ul')
);
В результате выполнения шаблона элемент menu__inner
будет добавлен как отдельный узел в HTML-дерево:
<menu class="menu">
<ul class="menu__inner"> // добавлен новый элемент menu__inner
<li class="menu__item"></li>
<li class="menu__item"></li>
</ul>
</menu>