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

  • Tutorial


Поскольку я предпочитаю методологию БЭМ, начав работать с 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. Повышенная специфичность. Данная проблема всегда становится заметна спустя время и часто возлагается на плечи тех, кто не создавал её...

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

Поскольку одна из главных задач это возможность переиспользовать код, то мы не можем назвать наши наклейки 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 {...}


Заключение


Это очень маленький кусочек кода, в котором скрыто много смысла.

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

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

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

Вы применяете БЭМ методологию в своих проектах?

  • 27,4%Да (Я front-end разработчик)20
  • 1,4%Да (Я back-end разработчик)1
  • 28,8%Да (Я full-stack разработчик)21
  • 11,0%Нет (Я front-end разработчик)8
  • 5,5%Нет (Я back-end разработчик)4
  • 16,4%Нет (Я full-stack разработчик)12
  • 12,3%Нет, но использую другую методологию9
  • 12,3%Нет, я не использую никакую методологию9
Поддержать автора
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 8

    +4
    За позиционирование отвечает родитель, а не сам блок. Это написано даже на сайте bem.info. Смысл в том, что блок может находиться в разных местах, он понятия не имеет, где именно, и отвечает только за то, что происходит внутри него. А вот родитель уже позиционирует свой элемент, который по совместительству является блоком.
    Сейчас у вас переиспользование блоков стремится к нулю. А смысл тогда разбивать всё на блоки и элементы? Можно и просто обозвать без всяких БЭМов. Всё так же будет хорошо жить.
      –2
      За позиционирование отвечает родитель, а не сам блок.

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

        Ну как сказать:
        Внешняя геометрия и позиционирование
        В CSS по БЭМ стили, отвечающие за внешнюю геометрию и позиционирование, задаются через родительский блок.

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

        И что будет, если я захочу использовать .sticker вне .stickers? Всё сломается. Потому что расположение определяет контекст родителя.
          –3
          В целом как раз таки нет, вы можете использовать стики точечно там где захотите, единственное, что да в таком случае гораздо лучше будет использовать позиционирование .sticker либо как микс либо как модификатор, с этим полностью согласен.

          .stickers так же является блоком и содержит в себе элементы и в целом конструкция может выглядеть так:

          stickers__sticker sticker .sticker_position_0

          Но я специально упустил это за ненадобностью и для простоты
            +1

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

              –2
              Обойтись можно и вложенностью, но нужно с чего-то начинать, я хочу привлечь внимание тех кто работает с opencart и такие детали их только отпугнут, но я уже говорил, что с вами согласен по поводу позиционирования и добавил примечание в статью
      0
      Opencart по умолчанию построен на вложенных селекторах, как шаблон, так и админка.


      И это самый маленький и тоненький из минусов OpenCart )

      По теме: я бы вложил .sticker как элемент блока .stickers, все-таки, они как таковые без него не существуют (вам обязательно нужен контейнер с relative). И более точно дал бы название, например product-stickers, потому что считаю что просто stickers — слишком абстрактно)
        0
        я бы вложил .sticker как элемент блока .stickers

        Согласен, тут я сделал небольшое уточнениепо этому поводу habr.com/ru/post/457016/#comment_20310488

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое