[Из песочницы] Организация адаптивной верстки в БЭМ с SCSS

Одна из главных сложностей возникающая у многих фронтенд-разработчиков при использовании методологии БЭМ в CSS — это способ организации адаптивной вёрстки. Как известно блоки и элементы должны быть независимы друг от друга, а также от контекста в котором они находятся, значит и от устройства на котором выводятся. При этом в разных разрешениях экрана блок фактически может иметь разное отображение. Речь пойдет о том как организовать адаптивность таких представлений, при этом сохранив возможность использовать каждое из них независимо от каких либо внешних факторов и друг от друга.

Часто встречаемая практика, при решении данной задачи сводится к написанию кода блока, а также модификаторов, которые используются совместно с ним, но действуют только для определённых типов устройств и их разрешений. Например блок .product и его модификаторы: .product_mobile, .product_tablet, .product_desktop. Модификаторы содержат в себе изменение представлений для конкретного класса устройств и разрешений, добавляя модификаторы к блоку мы можем управлять его адаптивностью. Чаще разработчики пренебрегают данной практикой, задавая все эти состояния в самом блоке, получая на выходе адаптивный блок для всех устройств. С этой стратегией не возникает проблем до тех пор пока не возникает необходимость на мобильном устройстве (*_mobile) выводить представление для планшета (*_tablet) или вовсе использовать на всех устройствах только один тип представления на одних страницах, и другой на отличных. Как организовать такое взаимодействие, сохранив при этом независимость и переносимость представлений рассмотрим далее.

Задача


Имеется 2 страницы, каталог и главная.
Имеется 3 представления блока товара: А, Б, С
И три разрешения экрана: Мобильное, Планшетное, ПК.
Необходимо главной странице сайта выводить отображение: А — мобильная версия. Б — планшетная. С — ПК.
На странице каталога: С — мобильный, А — планшетный, Б — ПК.
Также необходимо сохранить возможность переключения представления блока при добавлении к нему дополнительного модификатора.

На данном этапе становится очевидным что нам необходимо создать 6 модификаторов, и организовать их таким образом, чтобы они в разных ситуациях не перекрывали друг друга. Также нам необходимо сохранить возможность жесткого задания вида блока модификаторам, без потери его адаптивности. Становится очевидным что нам придется дублировать код в случаях («С» представление для ПК, «С» представление для мобильных устройств). Для избежания дублирования в данном случае хорошо подходят миксины SCSS, которых в конечном итоге понадобится всего 3 (по одной на каждое представление блока, А, Б, С), адаптивность и возможность переключения состояния достигается за счет особой организации миксин и их подключения.

Организация миксин


Можно использовать два способа организации миксин: сверстать каждое представление как самостоятельное или сверстать одно основное представление и расширять его. Рабочий пример:

Упрощённый пример
//Создаем представление по способу 1. Два блока одинаковых размеров, различается цвет.
@mixin type1-A() {
  width: 100px;
  height: 100px;
  background-color: red;
}

@mixin type1-B() {
  //две строки дублируются
  width: 100px;
  height: 100px;
  background-color: blue;
}

//Создаем представление по способу 2. Те же самые 2 блока, но вместо дублирования кода, будем изменять только одно свойство
@mixin type2-A() {
  width: 100px;
  height: 100px;
  background-color: red;
}

@mixin type2-B() {
  //Экономим 2 строки
  background-color: blue;
}

//Из-за различия способов определения миксин. Имеются различия в их подключении (cм. классы type1-b type2-b )
.t1-a {
  @include type1-A();
}

.t1-b {
  @include type1-B();
}

.t2-a {
  @include type2-A();
}

.t2-b {
  @include type1-A();
  @include type2-B();
}
//Также можно вынести заливку в отдельную миксину, подключая по 2 миксины на один блок. Код миксин опустим.
.A
{
  @include mix();
  @include mix_A();
} 

.B
{
  @include mix();
  @include mix_B();
} 


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

Перепишем миксины в несколько другом формате. Который и позволяет легко управлять конечными представлениями.

Код организации миксины

//$b - имя блока, $m - имя модификатора блока
@mixin present_type_1($b, $m:"") {
  #{$m}#{$b} {
    //Код основного блока .block-name
    //...code...
  }
  #{$m} #{$b}__item {
    //Код элемента .block-name__item
    //...code...
    &_modificator_value {
      //Код модификатора .block-name__item_modificator_value
      //...code...
    }
  }
}



Такой способ организации миксины позволяет подключить её множеством различных способов, делая при этом управление представлениями более понятным. Пусть у нас есть 3 миксины present_type_1, present_type_2, present_type_3, рассмотрим возможные варианты их подключения:
Подключение миксин
//Стили примерятся к блоку с селектором .some-block,  в данном случае представление 1
@include present_type_1(".some-block");

//Стили примерятся к ТОЛЬКО к блоку .some-block и модификатором .some-block_modificated, в данном случае представление 2
@include present_type_2(".some-block", ".some-block_modificated");

//еще один модификатор
@include present_type_3(".some-block", ".some-block_type_3");



Пример более приближенный к реальной жизни, плитка товара:

Организуем взаимодействие миксин для разных разрешений экранов:
Адаптивное подключение миксин

//Представление 1, будет основным, только для десктопа
@media all and (min-width:901px) {
  @include present_type_1(".some-block");
}

//Представление 2, только для планшетов
@media all and (min-width:601px) and (max-width:900px) {
  @include present_type_2(".some-block");
}

//Представление 3, только для мобил
@media all and (max-width:600px) {
  @include present_type_3(".some-block");
}

//Про этом по какой-то причине существует необходимость зафиксировать представление 2. Просто создадим еще один модификатор
@include present_type_2(".some-block", ".some-block_force-main-present");
//Обратное поведение, теперь в мобильной версии отображается представление 3, но для достижения такого эффекта придется переименовать блок, или добавить модификатор.
@media all and (min-width:901px) {
  @include present_type_3(".some-another-block");
}

@media all and (min-width:601px) and (max-width:900px) {
  @include present_type_2(".some-another-block");
}

@media all and (max-width:600px) {
  @include present_type_1(".some-another-block");
}

//Еще один пример. Пусть всегда необходимо представление 3, но в планшетной версии, будет представление 2, при наличии модификатора.
@include present_type_3(".some-block");
@media all and (min-width:601px) and (max-width:900px) {
  @include present_type_2(".some-block", ".some-block_tablet");
}


Плитка с адаптивом:

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

Полный пример с решением задачи описанной в начале статьи:

Бонус


Отступим от методологии БЭМ. Бывает случаи когда удобнее модифицировать потомков, в зависимости от селектора их родителя. Например изменить представление всех товаров в плитке товаров, изменив класс на родителе. Разберем на примере модификаторов product_parent_vertical product_parent_horizontal.
Верстка
Отображение станет вертикальным
Отображение станет горизонтальным


Рассмотрим пример организации такого поведения:
Реализация взаимодествия
@mixin vertical($b, $m:""){/*...*/}
@mixin horizontal($b, $m:""){/*...*/}

//По умолчанию вертикальное представление
@include vertical(".product");

//Модификатор родителя, задаётся так же как и обычный модификатор блока, НО В КОНЦЕ СТАВИТСЯ ПРОБЕЛ
@include vertical(".product",".product_parent_vertical ");

//Горизонтальное представление, опять пробел в конце, чтобы можно было работать с родителем.
@include horizontal(".product",".product_parent_horizontal ");


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

Плитка с кодом бонусной части:

Заключение


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

Комментарии (1)

  • 6 апреля 2017 в 15:36

    –1


    Это всё хорошо, однако иметь возможность «go to definition» в IDE куда круче, чем эти вот миксины.

© Habrahabr.ru