Форум

Методологія

Інструментарій

Платформа

Спільнота

Рішення типових проблем веб-розробки з допомогою БЕМ

Методологія БЕМ задає правила іменування 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-код сторінки, щоб визначити всі залежності. Селектор завжди містить знання про те, на який блок або елемент впливають його правила (в даному випадку на елемент nav__item). Розробникам не доведеться думати про можливе існування кнопки <div class="button active">Натисни мене!</div>, так як її 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-класів показують, що:

  • Існує форма, реалізована блоком form.
  • Модифікатор form_search вказує на те, що мова йде про форму пошуку.
  • У форми є складові — вкладені елементи: поле form__field кнопка form__submit-button.

Дотримання угоди щодо іменування БЕМ дозволяє зрозуміти структуру блоку без докладного вивчення 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-правила. При виявленні помилки, виправлення необхідно буде внести тільки одну частину коду.

Міксувати можна не тільки блоки з елементами, але і інші БЕМ-сутності. Детальніше про варіанти використання міксів в БЕМ читайте у розділі Основні поняття БЕМ.

Якщо ви помітили помилку, або хочете доповнити статтю, ви завжди можете або написати нам про це на Гітхабі, або поправити статтю з допомогою prose.io.