Блог

Методология БЭМ на примере стикеров в opencart



Поскольку я предпочитаю методологию БЭМ, начав работать с opencart, я сразу же столкнулся с ужасными для меня вещами, это вложенные селекторы. Они повсюду! Начиная от шаблона по умолчанию, заканчивая практически всеми модулями и авторскими шаблонами. Почему так? Мне кажется тут ряд причин:
  1. Opencart по умолчанию построен на вложенных селекторах, как шаблон, так и админка.
  2. Большинство разработчиков, которые работают с opencart являются именно back-end разработчиками, они просто подхватили этот подход
  3. Есть ряд необходимых классов и id к котором привязываются как стандартный функционал opencart, так и авторские модули, и все те же back-end разработчики, а за ними и их последователи, по разным причинам просто не хотят ничего менять и плывут по течению.
Я ни в коем случае не хочу ничего плохого сказать в сторону back-end разработчиков, но многие из них действительно слабы во front-end и даже в верстке. Это мнение сложилось на основе общения с ними, совместной работы и в целом по их активности на тематических форумах opencart. Я подчеркиваю, что имею ввиду именно нишу разработчиков opencart.

Я делаю шаблоны с нуля по методологии БЭМ (насколько это возможно в рамках opencart) и могу с уверенностью сказать, что любой модуль заводится с пол пинка не зависимо от разметки. Модуль о котором ниже пойдет речь вообще не нуждается в каких-либо правках, все что нужно это упростить работу с ним и реализовать возможность его переиспользовать в других проектах. Я взял для примера именно этот модуль, так как он очень прост и не нужно отвлекаться на кучу лишнего кода, но в тоже время он содержит в себе все те проблемы, которые решает БЭМ. Это реально существующий модуль и таких модулей, да и просто шаблонов, очень много. Я считаю, что один боевой пример лучше сотни абстрактных.


Для начала я опишу суть проблемы. В одной из отечественных сборок opencart встроен модуль стикеров. Он выводит выбранный стикер в указанный угол:
Верхний левый / Верхний правый / Нижний левый / Нижний правый 
Вариантов стикеров без ограничений, но позиций максимум 4:



Теперь посмотрим на разметку и стили:



Что мы видим:
  1. Все стикеры вложены в блок image
  2.  Не смотря на то, что блок image по логике предназначен для хранения изображения товара, все стили стикеров привязаны именно к нему, а теперь посмотрим на весь css и особенно на вложенность в последних строках:
/*sticker*/
.image {
    position: relative;
}
.image .corner_0,
.image .corner_1,
.image .corner_2,
.image .corner_3 {
    height: 57px;
    width: 58px;
    position: absolute;
    z-index: 998;
}
.image .corner_0 {
    left: 0px;
    top: 0px;
}
.image .corner_1 {
    right: 0px;
    top: 0px;
}
.image .corner_2 {
    left: 0px;
    bottom: 0px;
}
.image .corner_3 {
    right: 0px;
    bottom: 0px;
}
.box-product .image .corner_0 img,
.box-product .image .corner_1 img,
.box-product .image .corner_2 img,
.box-product .image .corner_3 img {
    border: none;
    padding: 0px;
}
.box .box-product .image .corner_0 img,
.box .box-product .image .corner_1 img,
.box .box-product .image .corner_2 img,
.box .box-product .image .corner_3 img {
    width: 60%;
}


Если .image .corner_2 выглядел еще более менее приемлемо, то .box .box-product .image .corner_2 img уже выглядит не так оптимистично... В целом можно догадаться, что где-то у нас появится .box-product без родителя .box и применяться одни стили, а где-то с родителем другие, но тут перед нами всплывает ряд проблем:

  1. Если стикеры вынести за пределы .image, все стили отвалятся, а если мы захватим с собой .image и поместим в другом месте, то применяться стили .image там где они не нужны.
  2. Если вдруг переименуем image, который по логике не является хранилищем для стикеров или .box или .box-product, которые находятся еще выше и уж точно никак не говорят о том что стикеры к ним привязаны, в любом из этих случаев мы не получим ожидаемый результат.
  3. Что если мы захотим .image поместить на один уровень с .box-product? Опять что-то пойдет не так..
  4. Много повторяющихся селекторов в которых меняется только .corner_# и если вдруг мы изменим эту вложенность или захотим перенести код в другой шаблон, то придется менять её везде, а ведь еще могут быть медиа запросы, это просто бесполезная трата времени.
  5. Повышенная специфичность. Данная проблема всегда становится заметна спустя время и часто возлагается на плечи тех, кто не создавал её...
  6. Те кто знаком с методологией БЭМ давно знают об этом, а те кто не знаком я думаю ни раз сталкивались. Давайте попробуем решить эти проблемы.

Поскольку одна из главных задач это возможность переиспользовать код, то мы не можем назвать наши наклейки corner, как раньше, ведь возможно в другом проекте мы захотим, чтобы они были не в уголках, а по середине каждой из сторон или вообще выстроились в ряд, поэтому логично будет назвать соответственно просто sticker, но чтобы не зависеть от внешних блоков, поместим наши стики в контейнер stickers который может быть как независимым блоком, так и миксом для любого блока в карточке товара. Результат:



Внешне мы получили такой же результат, но вот разметка и стили теперь другие:

/* stickers */

.stickers {
    position: relative;
}

.sticker {

}

.sticker_position_0 {
       position: absolute;
    left: 0px;
    top: 0px;
}

.sticker_position_1 {
       position: absolute;
    right: 0px;
    top: 0px;
}

.sticker_position_2 {
       position: absolute;
    left: 0px;
    bottom: 0px;
}

.sticker_position_3 {
       position: absolute;
    right: 0px;
    bottom: 0px;
}

.sticker__img {
    border: none;
    padding: 0;
}


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

Каждый стикер имеет класс .sticker, который содержит в себе общие для всех стиков стили, например размер. А вот стили отвечающие за позициониорвание мы выносим в  модификатор с ключем position:


Примечание:
.sticker может быть как элементом .stickers:
<div class="stickers">
    <div class="stickers__sticker sticker sticker_position_2">
        <img class="sticker__img " src="#">
    </div>
</div>
так и самостоятельным блоком для точечной расстановки без контекста stickers.



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



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

Остается еще не решенная проблема с этими селекторами, которые мозолили глаза ранее:

.box-product .image .corner_3 img {....}

.box .box-product .image .corner_2 img  {....}

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

.box-product  .sticker__img {...}

.box  .sticker__img {...}


Заключение

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

Спасибо всем, кто дочитал до конца и надеюсь, что моя статья была полезна.

Данная стать написана мной для habr

Комментарии

  • Ваше имя:
    Ваш e-mail: