НовостиАдминистрированиеУправления хостингомАрхив
09 июня 2016

Битва с БЭМ: 10 самых распространенных проблем и способы их решения (часть 1)

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

1. Что делать с глубокой вложенностью элементов и внучатыми селекторами?

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

<div class=\"c-card\">
    <div class=\"c-card__header\">
        <!-- Here comes the grandchild… -->
        <h2 class=\"c-card__header__title\">Title text here</h2>
    </div>
    <div class=\"c-card__body\">
        <img class=\"c-card__body__img\" src=\"some-img.png\" alt=\"description\">
        <p class=\"c-card__body__text\">Lorem ipsum dolor sit amet, consectetur</p>
        <p class=\"c-card__body__text\">Adipiscing elit.
            <a href=\"/somelink.html\" class=\"c-card__body__text__link\">Pellentesque amet</a>
        </p>
    </div>
</div>

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

Давайте вспомним о том, как должно выглядеть название класса согласно методологии - Block\\Element Modifier, а не Block\_Element\_Element Modifier. Поэтому для решения этой проблемы достаточно избегать большого количества уровней в именовании, а если вы столкнетесь с необходимостью обращаться к пра-пра-пра-правнукам контейнера, попробуйте изменить структуру документа.

БЭМ не связан с DOM, и реальный уровень вложенности не имеет прямого отношения к методологии. Название класса должно отображать взаимодействие элементов.

<div class=\"c-card\">
    <div class=\"c-card__header\">
        <h2 class=\"c-card__title\">Title text here</h2>
    </div>
    <div class=\"c-card__body\">
        <img class=\"c-card__img\" src=\"some-img.png\" alt=\"description\">
        <p class=\"c-card__text\">Lorem ipsum dolor sit amet, consectetur</p>
        <p class=\"c-card__text\">Adipiscing elit.
            <a href=\"/somelink.html\" class=\"c-card__link\">Pellentesque amet</a>
        </p>
    </div>
</div>

То есть, все вложенные объекты попросту являются элементами в блоке c-card.

2. Длинные имена и сложная структура

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

  • C (Component) - базовые элементы структуры, именно их оформление отвечает за внешний вид страницы. Примеры именования: c-card, c-checklist.
    • L (layout module) - блоки разметки, классы, не имеющие отношения к оформлению элементов и использующиеся исключительно для позиционирования компонентов. Примеры именования: l-grid, l-container.
    • H (Helpers) - классы, выполняющие одну функцию. Довольно часто в них встречается !important, используются для позиционирования или изменения видимости объекта. Примеры именования: h-show, h-hide.
    • Is, Has (States) - описывают возможные состояния компонента. Более детально эти классы мы разберем в проблеме 6. Примеры именования: is-visible, has-loaded.
    • Js (JavaScript hooks) - классы, которые используются для связи компонентов со скриптами. Примеры именования: js-tab-switcher.

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

3. Обертки

Часто приходится использовать обертки (контейнеры), которые управляют выравниванием и позиционированием вложенных в них элементов. В таких случаях стоит использовать что-то вроде l-grid для обертки и l-grid__item для вложенных элементов первого уровня:

<ul class=\"l-grid\">
    <li class=\"l-grid__item\">
        <div class=\"c-card\">
            <div class=\"c-card__header\">
                […]
            </div>
            <div class=\"c-card__body\">
                […]
            </div>
        </div>
    </li>
    <li class=\"l-grid__item\">
        <div class=\"c-card\">
            <div class=\"c-card__header\">
                […]
            </div>
            <div class=\"c-card__body\">
                […]
            </div>
        </div>
    </li>
    <li class=\"l-grid__item\">
        <div class=\"c-card\">
            <div class=\"c-card__header\">
                […]
            </div>
            <div class=\"c-card__body\">
                […]
            </div>
        </div>
    </li>
    <li class=\"l-grid__item\">
        <div class=\"c-card\">
            <div class=\"c-card__header\">
                […]
            </div>
            <div class=\"c-card__body\">
                […]
            </div>
        </div>
    </li>
</ul>

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

К сожалению, в некоторых ситуациях это невозможно. Бывают случаи, когда сложно говорить о полноценном использовании сетки, или от вас требуют чего-то более семантичного. Тогда вам стоит использовать метки container или list. Например, l-cards-container для блоков или l-cards-list для списков.

4. Кросс-компоненты

Еще одна проблема, которая довольно часто возникает - это компоненты, оформление или позиционирование которых зависит от родительского контейнера. Давайте разберем на конкретном примере. Представим, что нам необходимо добавить c-button в card__body из нашего предыдущего примера. Кнопка это уже компонент, который имеет такую разметку:

<button class=\"c-button c-button--primary\">Click me!</button>

Если кнопки должны выглядеть одинаково вне зависимости от родительского компонента, то никаких проблем, мы можем написать что-то вроде:

<div class=\"c-card\">
    <div class=\"c-card__header\">
        <h2 class=\"c-card__title\">Title text here</h3>
    </div>
    <div class=\"c-card__body\">
        <img class=\"c-card__img\" src=\"some-img.png\">
        <p class=\"c-card__text\">Lorem ipsum dolor sit amet, consectetur</p>
        <p class=\"c-card__text\">Adipiscing elit. Pellentesque.</p>
        <!-- Our nested button component -->
        <button class=\"c-button c-button--primary\">Click me!</button>
    </div>
</div>

Однако, если мы хотим изменить стилизацию компонента в зависимости от того, в какой части страницы он находится, нам придется немного изменить подход. Например, мы можем воспользоваться кросс-компонентами:

<div class=\"c-card\">
    <div class=\"c-card__header\">
        <h2 class=\"c-card__title\">Title text here</h3>
    </div>
    <div class=\"c-card__body\">
        <img class=\"c-card__img\" src=\"some-img.png\">
        <p class=\"c-card__text\">Lorem ipsum dolor sit amet, consectetur</p>
        <p class=\"c-card__text\">Adipiscing elit. Pellentesque.</p>
        <!-- My *old* cross-component approach -->
        <button class=\"c-button c-card__c-button\">Click me!</button>
    </div>
</div>

Однако, это не самый надежный вариант. В приведенном примере, класс c-card__c-button попытается изменить одно или несколько свойств c-button, но его эффективность будет зависеть от порядка указания классов и специфичности свойств.

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

<button class=\"c-button c-button--rounded c-button--small\">Click me!</button>

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

5. Модификатор или новый компонент?

Это, наверное, одна из самых больших проблем методологии: четко определить момент, когда пора заканчивать использовать модификаторы и нужно создавать новый компонент. В нашем примере с c-card вам, возможно, придется создать c-panel, который будет иметь очень схожее оформление, но все-таки с некоторыми отличиями. Но чем же мы должны руководствоваться, выбирая между очередным модификатором и новым компонентом?

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

По материалам www.smashingmagazine.com