Укрощение каскада с помощью БЭМ и современных селекторов CSS PlatoBlockchain Data Intelligence. Вертикальный поиск. Ай.

Укрощение каскада с помощью БЭМ и современных селекторов CSS

БЭМ. Как, казалось бы, и все техники в мире фронтенд-разработки, написание 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(), чтобы отменить его специфичность.

Независимо от того, идете ли вы ва-банк на БЭМ-именовании или нет, я надеюсь, что мы можем согласиться с тем, что постоянство в специфичности селекторов — это хорошо!

Отметка времени:

Больше от CSS хитрости