BEM と最新の CSS セレクターでカスケードを調整する

ソースノード: 1759690

BEM。 フロントエンド開発の世界の一見すべてのテクニックのように、 BEM形式でCSSを書く 二極化することができます。 しかし、少なくとも私の Twitter バブルでは、より好まれる CSS 手法の XNUMX つです。

個人的にはBEMがいいと思いますし、使ったほうがいいと思います。 しかし、あなたがそうしない理由もわかります。

BEM についてのあなたの意見に関係なく、いくつかの利点があります。最大の利点は、CSS カスケードでの特異性の衝突を回避するのに役立つことです。 これは、適切に使用された場合、BEM 形式で記述されたすべてのセレクターが同じ特異性スコアを持つ必要があるためです (0,1,0)。 私は何年にもわたって多くの大規模な Web サイト (政府、大学、銀行など) の CSS を設計してきましたが、これらの大規模なプロジェクトでは、BEM が本当に優れていることがわかりました。 記述または編集しているスタイルがサイトの他の部分に影響を与えていないという確信がある場合、CSS を記述することは非常に楽しくなります。

実際には、特異性を追加することが完全に許容されると見なされる例外があります。 例: :hover & :focus 疑似クラス。 それらの特異性スコアは 0,2,0. もう XNUMX つは疑似要素です。 ::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() 引数リスト内で最も特異性が高い要素です。 したがって、すでに、使用できる XNUMX つの最新のセレクターの間にカスケード ラングリングの違いがあります。

信じられないほどパワフル :has() リレーショナル疑似クラスも ブラウザのサポートを急速に獲得 (そして以来、CSS の最大の新機能です。 グリッド、 私の愚見で)。 ただし、執筆時点では、ブラウザのサポート :has() 本番環境で使用するにはまだ十分ではありません。

これらの疑似クラスの XNUMX つを 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;
}

XNUMX 番目のセレクターはソース順で最後ですが、最初のセレクターのより高い特異性 (0,2,0) が勝ち、の色 .something--special 要素が設定されます red. つまり、BEM が適切に記述されており、選択した要素に .something 基本クラスと .something--special HTML で適用される修飾子クラス。

これらの疑似クラスを不注意に使用すると、カスケードに予期しない影響を与える可能性があります。 そして、特に大規模で複雑なコードベースでは、この種の不整合が後で頭痛の種になる可能性があります。

ダン。 んで、どうする?

私が言っていたことを思い出してください :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 には欠点があります。 ベテランの BEM 作成者は、複数の要素に修飾子クラスを条件付きで適用するために必要なテンプレート ロジックが不格好であることを痛感しているでしょう。 この例では、HTML テンプレートは条件付きで --featured 修飾子クラスから XNUMX つの要素 (.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 クラス名で記述されていません。 場合によっては、これらのスタイルはクラスをまったく使用せず、ID のみを使用します!

もう一度、 :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トリック