Методология БЭМ никогда не подразумевала того, что длинные названия классов в селекторах типа .block__element_modifier
будут записываться вручную. В Яндексе для этого сразу используются специальные утилиты, которые генерируют стили и разметку блока. Если вы используете препроцессоры для генерации CSS, то вам тоже не нужно каждый раз повторять названия блока или элементов. Сейчас я расскажу, как это делается.
Все препроцессоры позволят вкладывать селекторы друг в друга. Таким образом, строятся каскады.
.header {
.title {
font: bold 24px/1 sans-serif;
}
}
После трансформации получаем CSS:
.header .title {
font: bold 24px/1 sans-serif;
}
Вместо «родительской части» селектора можно использовать символ &
. Удобно, когда нужно применить свойства и выбранному элементу, и его потомкам или псевдо-классам.
.header {
a {
&, &:hover, &:focus {
color: white;
text-decoration: none;
}
}
}
CSS:
.header a,
.header a:hover,
.header a:focus {
color: white;
text-decoration: none;
}
LESS
(а с недавнего времени и SASS) подставляют в новый селектор вместо &
часть родительского селектора без каких-либо изменений. Так же нет ограничений на последующие или предыдущие символы в новом селекторе. Поэтому вместо каскада можно генерировать простые селекторы.
.header {
&__link {
&, &:hover, &:focus {
color: white;
text-decoration: none;
}
}
}
CSS:
.header__link,
.header__link:hover,
.header__link:focus {
color: white;
text-decoration: none;
}
Аналогично формируем простые селекторы для модификаторов элементов.
.header {
background: white;
&__title {
font: bold 24px/1 sans-serif;
&_featured {
font-size: 30px;
}
}
}
CSS:
.header {
background: white;
}
.header__title {
font: bold 24px/1 sans-serif;
}
.header__title_featured {
font-size: 30px;
}
Вложенные селекторы превратились в простые.
Важно знать
А теперь несколько простых правил, чтобы обладая этим знаниями вы «не прострелили себе ногу».
Название блока используется только один раз
Оно объявляется на первом уровне и нигде больше не повторяется. Исключения могу составлять только сложные случаи модификаторов. Однако, если вам это понадобилось, то скорее всего, стоит пересмотреть структуру элементов и модификаторов. Уверен, что найдётся способ упросить блок или декомпозировать его.
.block {
// стили блока
}
Элементы идут на втором уровне вложенности
.block {
// стили блока
&__element {
// стили элемента
}
&__title {
}
}
Иногда названия элементов будут причудливым образом сочетаться и захочется вложить один селектор элемента в другой. Не поддавайтесь этому искушению несмотря на то, что будет генерироваться правильный CSS.
.block {
&__element {
&-wrapper {
// Пример плохого элемента.
// Его будет трудно отыскать.
}
}
}
Никогда не делайте подобного. Такой селектор очень трудно будет найти в коде. В примере название элемента element-wrapper
разорвано на две части. Пишите название элементов полностью даже если они частично повторяют уже существующие.
Псевдо-классы, псевдо-элементы и модификаторы элементов допускается писать на третьем уровне вложенности
.block {
&__element {
&_modifier {
// стили модификатора элемента
}
&_modifier_value {
// модификатор со значением не разбиваем на части
}
&:hover {
// псевдо-класс — это тоже модификатор
}
}
}
Стили элемента и его вариации группируются естественным образом, что позволит быстрее их локализовать в исходном коде.
Принято, что модификатор элемента не влияет на другие элементы. Располагая их на третьем уровне иерархии, мы защищаемся от возможных ошибок. Могу предположить ситуацию когда некоторые модификаторы будет удобнее расположить на втором уровне, продублировав название элемента.
.block {
&__element_active {
// модификатор элемента на втором уровне
}
&__element_modifier_good {
// пример модификатора со значением
}
}
На практике мне больше нравится именно такая запись — модификатор на втором уровне. Также считаю, что недопустимо разбивать название модификатора на части, а так же не стоит отделять значение модификатора от его названия. Излишняя структурированность может ухудшить читаемость. Легко потерять текущий контекст. Помните об этом.
Модификаторы блока могут участвовать в каскаде и располагаются на втором уровне
Модификаторы блока принято использовать, чтобы повлиять на внешний вид самого блока и его дочерних элементов, но не вложенных в него других блоков.
.block {
background: white;
&__title {
color: black;
font: bold 24px/1 sans-serif;
}
&_featured {
background: black;
}
&_featured &__title {
color: white;
font-size: 30px;
}
}
Изящность кода чуть теряется из-за того, что нам важно сохранить значение &
. Как только мы перейдём на следующий уровень вложенности, то использовать его в названии элемента уже не сможем — нужно будет писать полное название селектора вместе с названием блока, чего мы всячески избегаем.
.block {
background: white;
}
.block__title {
color: black;
font: bold 24px/1 sans-serif;
}
.block_featured {
background: black;
}
.block_featured .block__title {
color: white;
font-size: 30px;
}
Заключение
- Препроцессоры избавляют нас от повторения одних и тех же частей простых селекторов.
- В исходном мы имеем имитацию каскада, который препроцессором трансформируется в простой селектор.
- Использование такого подхода уменьшает опечатки и структурные ошибки.
Эта статья была любезно переведена на английский язык Варей Степановой и опубликована во «Frontend Babel» — Generating BEM selectors with CSS preprocessors.
Коментарии к заметке
Есть мнение, что выигрыш, достигаемый такими конструкциями, велик примерно настолько же, насколько возникает проблема поиска селектора; практическая польза при этом допущении выходит ровно в ноль.
Утверждение про опечатки и структурные ошибки представляется спорным при использовании глубины вложенности больше двух с последующим многократным рефакторингом; велик риск где-то опечататься и поставить один _ вместо __ — и всё, найти ошибку будет уже намного проблематичнее, чем в случае простого селектора.
Я бы отказался от использования таких конструкций для элементов блока и оставил бы только для модификаторов.
Смотри, в поиске элемента нет проблемы когда блоки лежат в разных файлах. Мелкие «вспомогательные» блоки можно класть в тот же файл, что и «основной». Мы в проектах стараемся не допускать распухания файлов. Файлы стилей мы стараемся именовать так же как и блоки. По этому поиск самого блока тоже не составляет труда. В крайнем случае можно сделать поиск по всему проекту.
С опечатками всё сложно. Если орфография ещё хоть как-то проверяется IDE, то с подчёркиваниями нужно быть внимательнее. Так же опечатки могут быть в HTML. Я не знаю способа кроме как bem-tools, чтобы совладать с этим.
Интересная дискуссия из твиттера:
Продолжение по ссылке — https://twitter.com/abrosimov/status/476703861143785473
Я использую в less следующий подход: в файле блока определяется переменная, которую можно использовать на уровнях вложенности глубже первого:
Спасибо, Макс. Интересный способ. Попробую как-нибудь его на практике.
Мы в Стайлусе планируем добавить больше возможностей для получения частей дерева вложенности: https://github.com/LearnBoost/stylus/issues/1240 — см. “Relative references” и “Partial reference modifier”.
После их реализации вариант с переменными будет выглядеть грязным хаком :)
Рома, в контексте этой статьи хотелось бы иметь референс на блок независимо от того где находишься. А так получается нужно понимать где находишься и возвращаться на один, два и т.п. шага назад.
Странно, но у меня не работают примеры из статьи — нельзя в моём sass’е делать такие селекторы, выдает ошибку. Использую Grunt и npm-модуль grunt-contrib-compass версии 0.8.0, может это в нем проблема?
Использую Sass 3.3.8, Compass 0.12.6, grunt-contrib-compass 0.8.0 — не работают примеры из статьи.
Последняя релизная версия Compass не работает с Sass 3.3.x. Если запускать компилятор из командной строки, то он отработает без ошибок.
В интернетах пишут, что нужно устанавливать альфа-версию Compass:
Для меня это сработало.
Невероятно, всё работает. Как же я этого ждал! Спасибо!
И, конечно, Стилус. Сам пришёл к примерно таким же правилам.
Саблайм с синтаксисом LESS не подсвечивает элементы как блоки, это очень сбивает
А ничего что по БЭМ каскадность — смертный грех?
Виталий, по методологии БЭМ каскадность вполне себе может существовать в пределах одного блока. Так работают «Модификаторы». А вот цепочки селекторов, когда что-то из вне влияет на блок или какие-то части блока влияют на другие блоки, разумеется, недопустимы.
А быть в такой ситуаци с использованием родительского селектора &?
К примеру, есть некий блок:
и к примеру, я хочу чтобы у модалки с неким модификатором, хеадер и боди выглядели бы по-другому:
Если я правильно понял, то в примере используется модификатор блока. Смотрите соответствующий раздел в статье.
Мы бы сделали это так:
А подскажите, еще один вопрос меня мучает.
Предположим есть блок с телефоном:
В шапке он выглядит по-умолчанию, а например в футере, он должен выглядеть по-другому. Если каскады запрещены, то так нельзя делать, правильно я понимаю?
Лучше сделать модификатор у phone?
Или нет?
Совершенно верно. В таком случае делается модификатор у блока
.phone
.А может ли модификатор блока менять оформление своих элементов каскадом? Например:
Он именно так и работает. Это единственный случай, когда генерируется не простой селектор, а селектор потомка.
Пытаемся потихоньку переходить на БЭМ, но есть для нас очевидные минусы, или возможно мы не так что-то делаем. Например, есть модуль (блок) модального окна, блок телефона, блок кнопки. В шапке, примеру телефон выглядит обычно, а в модальном окне телефон выглядит по-другому. И соответственно, сложно для каждого элемента в модалке: для телефона, кнопки и еще кучи элементов ведь все составное у нас, ну вот сложно для каждого элемента писать некий модификатор, ведь проще написать один модификатор у модалки и уже отталкиваясь от него изменять внешний вид телефона, кнопки и т.д да и кода в html будет меньше.
Тахир, если разница в стилях, казалось бы, одного и того же элемента существенна, то я бы сделал их разными блоками. Номера телефона может быть элементом родительского блока, а не самостоятельным блоком. Тогда не будет проблем с переопределением. В блок, обычно выносится контент, который имеет смысл без своего окружения. Например, кнопка — это блок, модалка — это блок, а всё остальное — это содержимое модалки, которое никогда вне этой модалки не появляется.
Пробуйте разные варианты декомпозиции. Навык приходит с практикой.
Методология БЭМ никогда не предполагала, что HTML/CSS будут писать руками. Виталий с командой сразу делал инструментарий для генерации шаблонов и стилей. Они даже не используют препроцессоры. Когда методология пошла в массы, то большинство начало использовать только особое именование классов. Мы в компании не пользуемся инструментами БЭМ, но построили процесс так, чтобы не писать каждый раз много букв в стилях и шаблонах. А то, что получается в HTML и CSS уже никого не интересует, так как эти файлы генерируются при сборке и никогда не редактируются руками.