Frontender Magazine

Чуть больше Sass для (БЭ)Модификаторов

В начале 2013 года синтаксис «Блок, Элемент, Модификатор» (БЭМ), применяемый к CSS, стал крайне популярной методологией организации кода, добавляющей определенное единообразие в проекты. Если брать в целом, то я просто обожаю БЭМ. Он действительно хорошо структурирован, и для меня это важно. Единственное, что меня волновало все это время, да и не только меня, но и большинство моих коллег разработчиков — это то, насколько длинными и избыточными становились значения атрибута class у элементов. Особенно, когда дело доходило до модификаторов.

<button class="button button--green button--rounded button--large">

Обособленные модификаторы: сокращенный синтаксис

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

HTML

<button class="button -green -rounded -large">

SCSS

.button {
    &.-green {...}
    &.-rounded {...}
    &.-large {...}
}   

Такой ход обеспечивает наглядное отделение элементов и модификаторов, позволяя при этом избегать повторения в имени класса. И да, использование дефиса в качестве первого символа в селекторе валидно, а вот использование двойного дефиса — нет. Если вас действительно беспокоят селекторы по нескольким классам, то возможно, что у вас просто остались плохие воспоминания об ужасах времен IE6. Но, поверьте, вам больше действительно не стоит о нём беспокоиться.

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

SCSS

.button {
    &.-hoverable {
        &:hover {
            opacity: 0.75;
        }
    }
}

.overrides {
    &.-disabled {
        opacity: 0.25;
    }
}   

HTML

<button class="button -hoverable overrides -disabled">Disabled</button> 

Пример конечно же выдуманный, но я думаю, что вы уловили смысл. При наведении курсора на кнопку ее прозрачность не изменяется, потому что свойства, определенные для селектора .button.-hoverable, перекрывается другими свойствами, указанными для селектора .overrides.-disabled, обладающего той же специфичностью, но определенного в таблице позже.

Сохраненные вариации: расширение с помощью Sass

Гибкость использования модификаторов в разметке — это круто, но когда я замечаю часто повторяющиеся группы свойств, я предпочитаю комбинировать их в моей таблице стилей. Директива @extend в Sass отлично подходит для этого.

SCSS

.button--save {
    @extend %button;
    @extend %button--large;
    @extend %button--rounded;
    @extend %button--green;
}   

HTML

<button class="button--save">Save</button>  

Весьма наглядно. В этом примере вы должны обратить свое внимание на следующее: 1) я использую селектор-плейсхолдер % в Sass; 2) и я все еще использую обычный синтаксис БЭМ элемент--модификатор.

Сначала я определяю все свои стили для селектора-плейсхолдера, и таким образом, я могу расширить их для других классов с помощью директивы @extend.

%button {
    background: #45beff;
    border: none;
    padding: 1em 2em;
    font-size: 16px;

    &:hover {
        opacity: 0.75;
    }
}

%button--green {
    background: #3efa95;
}

%button--red {
    background: #ff3a6a;
}

%button--large {
    font-size:20px;
}

%button--rounded {
    border-radius: 10px;
}

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

.button {
    @extend %button;

    &.-green {
        @extend %button--green;
    }

    &.-large {
        @extend %button--large;
    }
}

.button--delete {
    @extend %button;
    @extend %button--large;
    @extend %button--rounded;
    @extend %button--red;
}

Посмотреть пример на CodePen.

От БЭМ к БЭВМ?

После всего, что сказано и сделано выше, паттерн БЭМ блок__элемент--модификатор для меня трансформировался во что-то более похожее на блок__элемент--вариация -модификатор (БЭВМ), дополненный внутренними Sass-селекторами вида %модификатор.

// Внутренний модификатор
%button--red {
    background: #ff3a6a;
}

// Элемент
.button {
    // Модификатор
    &.-red {
        @extend %button--red;
    }
}

// Вариация
.button--delete {
    @extend %button;
    @extend %button--red;
}

Мне все это нравится, а что думаете вы? Удобно? Странно? Давайте обсудим!

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Dan Tello
Автор:
Dan Tello
GitHub:
greypants
Twitter:
@dantello5
Email:
dan.tello@viget.com
Сергей Смольников
Переводчик:
Сергей Смольников
Мой Круг:
http://sergey-smolnikov.moikrug.ru/
Twitter:
@smolnikov
GitHub:
smolnikov

Комментарии (11 комментариев, если быть точным)

Автар пользователя
alexeyraspopov

button--red

Ох, очень семантичное имя класса, супер!

Автар пользователя
pvpshoot

а если red нужно сделать грин, переписывать классы элементам?

Автар пользователя
zenwalker

Однако, зачастую дублирование названия элемента является избыточным.

Иногда ваш подход может привести к конфликту модификаторов, если вдруг придется к одному блоку примиксовать другой.

Автар пользователя
awinogradov

Stylus нотация выглядит привлекательнее

Автар пользователя
MrFranke

"а если red нужно сделать грин, переписывать классы элементам?" - пример очень плохой, в реальном проекте лучше использовать более абстрактные имена классов:

button--red => button--alert Тогда при замене красного на берюзовый не будет никаких проблем в виде переписывания классов у кнопок

Автар пользователя
smolnikov

Обратите внимание на то, что автор не призывает использовать классы вида .button--red. Он использует селектор-плейсхолдер % в SASS, чтобы затем включить свойства с помощью @extend и получить .button--delete.

Автар пользователя
mikaspell

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

Автар пользователя
ihorzenich

Мы к такой же идее пришли в 2013 году: https://github.com/ideus-team/guidelines/blob/master/frontend/bem.md

Автар пользователя
ihorzenich

Внутренние Sass-селекторы вида %модификатор - это Sass реализация абстрактных блоков BEM (i-блоков). Это BEM, нет никакого БЭВМ.

css %i-block { // стили абстрактного блока } .b-block { @extend %i-block; }

Автар пользователя
ilyar

Чуть больше Stylus для (БЭ)Модификаторов в классической нотации #b_ http://codepen.io/ilyar/pen/qERXjM

Автар пользователя
ihorzenich

Пришёл к тому, что абстрактные классы #b_ CSS в Sass всё же лучше реализовывать через mixin+include, а не %extend-only-selector+extend

css @mixin i-block { // стили абстрактного блока } .b-block { @include i-block; }

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

Опять-таки раз мы и так реализуем все связи и зависимости через язык высокого уровня (Sass), то нам нет нужны поддерживать связность силами CSS (когда на выходе классы перечисляются через запятую) и логично чтоб и тут её не было.

Да и Harry Roberts одобряет http://csswizardry.com/2014/11/when-to-use-extend-when-to-use-a-mixin/ ведь „Repetition in a compiled system is not a bad thing: repetition in source is a bad thing.“

Примеры: 1) Через mixin+include (работает):

``` .b-form {

@mixin i-styledElement { /* styles for form element… */ }

input { @include i-styledElement; height: 56px; }

.mobile &__item.-styled select { /* styled select on mobiles */ appearance: none; @include i-styledElement; } } ```

Компилируется в

``` .b-form input { /* styles for form element… */ height: 56px; }

.mobile .b-form__item.-styled select { /* styled select on mobiles / appearance: none; / styles for form element… */ }

```

2) Через %extend-only-selector+extend (не работает)

``` .b-form {

%i-styledElement { /* styles for form element… */ }

input { @extend %i-styledElement; height: 56px; }

.mobile &__item.-styled select { /* styled select on mobiles */ appearance: none; @extend %i-styledElement; } } ```

Компилируется в:

``` .b-form input, .b-form .mobile .b-form__item.-styled select, .mobile .b-form__item.-styled .b-form select { /* styles for form element… */ }

.b-form input { height: 56px; }

.mobile .b-form__item.-styled select { /* styled select on mobiles */ appearance: none; } ```

Сравните.
Код от mixin+include меньше и аккуратней.

Обратите внимание — extend вставляет класс родительского блока (.mobile .b-form__item.-styled .b-form select) при создании нового элемента на основе %i-блока, т.к. код %i-блока находится внутри родительского класса (.b-form). Этот пример заработает только если %i-блок вынести за пределы b-form. А если мы будет создавать элемент внутри media querie то вообще ничего не поможет — extend там не работает.