БЭМ. Как, казалось бы, и все техники в мире фронтенд-разработки, написание CSS в формате БЭМ может быть полярным. Но это — по крайней мере, в моем пузыре Twitter — одна из самых популярных методологий CSS.
Лично я думаю, что БЭМ хорош, и я думаю, что вы должны его использовать. Но я также понимаю, почему вы не можете.
Независимо от вашего мнения о БЭМ, он предлагает несколько преимуществ, самым большим из которых является то, что он помогает избежать конфликтов специфичности в каскаде CSS. Это связано с тем, что при правильном использовании любые селекторы, написанные в формате БЭМ, должны иметь одинаковую оценку специфичности (0,1,0
). Я разработал CSS для множества крупных веб-сайтов на протяжении многих лет (вспомните правительство, университеты и банки), и именно в этих более крупных проектах я обнаружил, что БЭМ действительно сияет. Написание CSS намного веселее, когда вы уверены, что стили, которые вы пишете или редактируете, не влияют на какую-либо другую часть сайта.
На самом деле есть исключения, когда добавление конкретики считается вполне приемлемым. Например: :hover
и :focus
псевдоклассы. Они имеют показатель специфичности 0,2,0
. Другой - псевдоэлементы - например ::before
и ::after
- которые имеют показатель специфичности 0,1,1
. Однако в оставшейся части этой статьи давайте предположим, что нам не нужна никакая другая специфичность. 🤓
Но на самом деле я здесь не для того, чтобы продавать вам БЭМ. Вместо этого я хочу поговорить о том, как мы можем использовать его вместе с современными селекторами CSS — подумайте :is()
, :has()
, :where()
и т. д. — чтобы получить еще БОЛЕЕ контроль Каскад.
Что такого в современных селекторах CSS?
Ассоциация Спецификация CSS-селекторов уровня 4 дает нам несколько мощных новых способов выбора элементов. Некоторые из моих любимых включают :is()
, :where()
качества :not()
, каждый из которых поддерживается всеми современными браузерами и в настоящее время безопасен для использования практически в любом проекте.
:is()
и :where()
в основном одно и то же, за исключением того, как они влияют на специфичность. Конкретно, :where()
всегда имеет показатель специфичности 0,0,0
. Да, даже :where(button#widget.some-class)
не имеет специфики. При этом специфика :is()
является элементом в списке аргументов с наивысшей специфичностью. Итак, у нас уже есть различие между двумя современными селекторами, с которым мы можем работать.
Невероятно мощный :has()
реляционный псевдокласс также быстро набирает поддержку браузеров (и это самая большая новая функция CSS с тех пор, как сетка, по моему скромному мнению). Однако на момент написания статьи поддержка браузерами :has()
еще недостаточно хорош для использования в производстве.
Позвольте мне воткнуть один из этих псевдоклассов в мой БЭМ и…
/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
/* styles for all somethings, except for the special somethings */
}
Упс! Видите этот показатель специфичности? Помните, что в БЭМ мы в идеале хотим, чтобы все наши селекторы имели показатель специфичности 0,1,0
, Почему 0,2,0
Плохо? Рассмотрим этот же пример в расширенном виде:
.something:not(.something--special) {
color: red;
}
.something--special {
color: blue;
}
Несмотря на то, что второй селектор является последним в исходном порядке, более высокая специфичность первого селектора (0,2,0
) выигрывает, а цвет .something--special
элементы будут установлены в red
. То есть, если ваш БЭМ написан правильно и выбранный элемент имеет оба .something
базовый класс и .something--special
класс модификатора, примененный к нему в HTML.
При неосторожном использовании эти псевдоклассы могут неожиданным образом повлиять на Cascade. И именно такие несоответствия могут создать головную боль в будущем, особенно при работе с большими и сложными кодовыми базами.
Данг. Что теперь?
Помните, что я говорил о :where()
а то что его специфика нулевая? Мы можем использовать это в своих интересах:
/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
/* etc. */
}
Первая часть этого селектора (.something
) получает свою обычную оценку специфичности 0,1,0
. Но :where()
— и все, что внутри него — имеет специфику 0
, что не увеличивает специфичность селектора.
:where()
позволяет нам вкладывать
Люди, которые не так сильно заботятся о специфичности, как я (а таких, наверное, много, если честно), довольно хорошо справлялись со вложенностью. С некоторыми беззаботными нажатиями на клавиатуре мы можем получить вот такой CSS (обратите внимание, что для краткости я использую Sass):
.card { ... }
.card--featured {
/* etc. */
.card__title { ... }
.card__title { ... }
}
.card__title { ... }
.card__img { ... }
В этом примере у нас есть .card
составная часть. Когда это «избранная» карта (используя .card--featured
класс), заголовок и изображение карты должны быть оформлены по-разному. Но, как мы сейчас знаете, приведенный выше код приводит к показателю специфичности, несовместимому с остальной частью нашей системы.
Заядлый знаток специфики мог бы сделать это вместо этого:
.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }
Это не так уж плохо, верно? Честно говоря, это красивый CSS.
Однако в HTML есть обратная сторона. Опытные авторы БЭМ, вероятно, до боли знакомы с неуклюжей логикой шаблонов, необходимой для условного применения классов модификаторов к нескольким элементам. В этом примере шаблон HTML должен условно добавить --featured
класс модификатора до трех элементов (.card
, .card__title
качества .card__img
), хотя, вероятно, даже больше в реальном примере. это много if
заявления.
Ассоциация :where()
selector может помочь нам написать гораздо меньше шаблонной логики — и меньше БЭМ-классов для загрузки — без добавления уровня специфичности.
.card { ... }
.card--featured { ... }
.card__title { ... }
:where(.card--featured) .card__title { ... }
.card__img { ... }
:where(.card--featured) .card__img { ... }
Вот то же самое, но в Sass (обратите внимание на завершающий амперсанды):
.card { ... }
.card--featured { ... }
.card__title {
/* etc. */
:where(.card--featured) & { ... }
}
.card__img {
/* etc. */
:where(.card--featured) & { ... }
}
Следует ли вам выбрать этот подход вместо применения классов модификаторов к различным дочерним элементам, зависит от личных предпочтений. Но хотя бы :where()
дает нам выбор сейчас!
Как насчет не-БЭМ HTML?
Мы не живем в идеальном мире. Иногда вам нужно иметь дело с HTML, который находится вне вашего контроля. Например, сторонний скрипт, который внедряет HTML, который вам нужно стилизовать. Эта разметка часто не написана с именами классов БЭМ. В некоторых случаях эти стили используют не классы, а идентификаторы!
Вновь :where()
имеет нашу спину. Это решение немного хакерское, так как нам нужно сослаться на класс элемента где-то выше по дереву DOM, о существовании которого мы знаем.
/* ❌ specificity score: 1,0,0 */
#widget {
/* etc. */
}
/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
/* etc. */
}
Однако ссылка на родительский элемент кажется немного рискованной и ограничительной. Что, если этот родительский класс изменится или по какой-то причине его не будет? Лучшим (но, возможно, столь же хакерским) решением было бы использование :is()
вместо. Помните, специфика :is()
равен самому конкретному селектору в его списке селекторов.
Таким образом, вместо того, чтобы ссылаться на класс, который, как мы знаем (или надеемся!) существует с :where()
, как в приведенном выше примере, мы могли бы сослаться на составленный класс и тег.
/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
/* etc. */
}
Вездесущий body
поможет нам выбрать наш #widget
элемент, а также наличие .dummy-class
класс внутри того же :is()
дает body
селектор имеет тот же показатель специфичности, что и класс (0,1,0
)… и использование :where()
гарантирует, что селектор не станет более конкретным, чем это.
Это оно!
Вот как мы можем использовать современные функции управления специфичностью :is()
и :where()
псевдоклассы наряду с предотвращением конфликтов специфичности, которые мы получаем при написании CSS в формате БЭМ. И в не столь отдаленном будущем, консолидировать :has()
получает поддержку Firefox (в настоящее время он поддерживается за флагом на момент написания статьи) мы, вероятно, захотим соединить его с :where(), чтобы отменить его специфичность.
Независимо от того, идете ли вы ва-банк на БЭМ-именовании или нет, я надеюсь, что мы можем согласиться с тем, что постоянство в специфичности селекторов — это хорошо!