EN RU
Форум

Методология

Технологии

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

Библиотеки

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

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, необходимо:

Файловая структура проекта:

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>