Приборкання каскаду за допомогою BEM і сучасних селекторів CSS PlatoBlockchain Data Intelligence. Вертикальний пошук. Ai.

Приборкання каскаду за допомогою BEM і сучасних селекторів CSS

БЕМ. Як, здавалося б, і всі методи у світі інтерфейсної розробки, написання CSS у форматі BEM може бути поляризаційним. Але це – принаймні в моїй бульбашці Twitter – одна з найбільш популярних методологій CSS.

Особисто я вважаю, що БЕМ це добре, і я вважаю, що вам слід його використовувати. Але я також розумію, чому ви можете ні.

Незалежно від вашої думки про BEM, він пропонує кілька переваг, найбільшою з яких є те, що він допомагає уникнути конфліктів специфічності в CSS Cascade. Це тому, що за правильного використання будь-які селектори, написані у форматі BEM, повинні мати однакову оцінку специфічності (0,1,0). Протягом багатьох років я розробляв CSS для багатьох великих веб-сайтів (вважаю, уряд, університети та банки), і саме в цих великих проектах я виявив, що BEM справді сяє. Писати CSS набагато веселіше, коли ви впевнені, що стилі, які ви пишете чи редагуєте, не впливають на іншу частину сайту.

Насправді існують винятки, коли додавати конкретики вважається цілком прийнятним. Наприклад: :hover та :focus псевдокласи. Вони мають оцінку специфічності 0,2,0. Інший — псевдоелементи — лайк ::before та ::after — які мають оцінку специфічності 0,1,1. Однак для решти цієї статті припустімо, що ми не хочемо жодної іншої специфіки. 🤓

Але я тут не для того, щоб продавати вам BEM. Натомість я хочу поговорити про те, як ми можемо використовувати його разом із сучасними селекторами 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() ще недостатньо добре для використання у виробництві.

Дозвольте вставити один із цих псевдокласів у мій BEM і…

/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
  /* styles for all somethings, except for the special somethings */
}

Ой! Бачите цю оцінку специфічності? Пам’ятайте, що в ідеалі з BEM ми хочемо, щоб усі наші селектори мали оцінку специфічності 0,1,0. Чому це 0,2,0 погано? Розглянемо цей самий приклад, розширений:

.something:not(.something--special) {
  color: red;
}
.something--special {
  color: blue;
}

Незважаючи на те, що другий селектор є останнім у вихідному порядку, перший селектор має вищу специфічність (0,2,0) виграє, а колір .something--special буде встановлено значення елементів red. Тобто, припускаючи, що ваш BEM написаний належним чином і вибраний елемент містить обидва .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 { ... }

У цьому прикладі ми маємо a .card компонент. Коли це «пропонована» картка (за допомогою .card--featured клас), заголовок і зображення картки потрібно оформити іншим стилем. Але, як ми зараз Знайте, наведений вище код призводить до оцінки специфічності, яка не відповідає решті нашої системи.

Завзятий ботанік міг би зробити це замість цього:

.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }

Це не так вже й погано, правда? Чесно кажучи, це прекрасний CSS.

Однак у HTML є мінус. Досвідчені автори BEM, ймовірно, до болю усвідомлюють незграбну логіку шаблону, яка потрібна для умовного застосування класів-модифікаторів до кількох елементів. У цьому прикладі шаблон HTML потрібно умовно додати --featured клас модифікатора до трьох елементів (.card, .card__title та .card__img), хоча, ймовірно, навіть більше в реальному прикладі. Це багато if заяви.

Команда :where() селектор може допомогти нам написати набагато менше логіки шаблонів — і менше класів BEM для завантаження — без збільшення рівня специфічності.

.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() дає нам вибір зараз!

А як щодо не-BEM HTML?

Ми живемо не в ідеальному світі. Іноді вам потрібно мати справу з HTML, який знаходиться поза вашим контролем. Наприклад, сценарій третьої сторони, який вставляє HTML, який потрібно стилізувати. Ця розмітка часто не записується з іменами класів BEM. У деяких випадках ці стилі взагалі не використовують класи, крім ідентифікаторів!

Знову :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 у форматі BEM. І в недалекому майбутньому, один раз :has() отримує підтримку Firefox (наразі це підтримується за прапором на момент написання), ми, ймовірно, захочемо поєднати його з :where(), щоб скасувати його специфічність.

Незалежно від того, чи йдете ви ва-банк на іменування BEM чи ні, я сподіваюся, ми можемо погодитися, що послідовність у специфічності селектора – це добре!

Часова мітка:

Більше від CSS-хитрощі