本付録は、メインシリーズであるWordPressのテーマ作成に役立ちそうな情報を補足していくパートだ。
前回は、設計手法の一つであるBEMの基礎を解説した。
一旦は3つのBEMエンティティがそれぞれ何であるかとそれらの違いを理解しておいてもらえれば、今回の内容に入れると思う。
不安なら前回の記事や公式サイトも参考にしながら進めてもらいたい。
以下がその記事だ。
さて、今回はもちろんBEMの続きだ。
前回のラストにも書いたように、まずModifierに関する注意点があるのでそれを補足する。
その後、Block同士をネストする部分や、他にも重要な考え方であるMixというものを解説していこう。
また、命名規則についても追加情報があるので、オマケとして追記することにする。
なお、本付録は以下の本を参考にしている。
気になる方は、実際に本屋で中身を見てみたり、買ってみたりしよう。
もう一つ、BEMの公式サイトも再掲しておく。
上にも書いた通り、こちらも併せて参照していただければより理解が深まると思う。
Modifierの注意点
早速本題へ、Modifierの注意点は大きく3つある。
- Modifierの責任範囲
- 一つのModifierによる複数要素の変更
- Modifier名の省略
一つずつ見ていこう。
Modifierの責任範囲
名前からなんとなく想像付くとは思うが、当てはまる考え方は7つ目のクラス名から見た目・機能・役割が想像できるだ。
前回の復習で、そもそもModifierはあるBlockやElementに追加情報を付与するものだった。
例えば文字を大きくしたり、それが選択されていることだったり、などなど。
ここで、同じElement(クラス名はblock__element
とする)に対して以下のような二つのModifierを付与したとしよう。
block__element_size_small
block__element_bg-color_red
一つ目は領域サイズを小さく、二つ目は背景色を赤にすることを想定している。
このとき、以下のようなCSSになっているとまずいことが起こる。
.block__element_size_small {
width: 160px;
}
.block__element_bg-color_red {
width: 200px;
background-color: red;
color: white;
}
今この部分だけ見ているのでそんなことにはならんだろ…と思うかもしれないが、規模が大きくなってくるとこんな状況が生まれる可能性もある。
ここでの問題点は二つある。
一つ目は、下のブロックは背景色を変更しているModifierのはずなのに横幅まで設定していること。
これにより、複数の設定が意図しない衝突を生んでしまう。
今回の場合、両方を一つのElementに付与すると、上の設定が下の設定にかき消されてしまう。
そのため、そのModifierでカバーすべき範囲…これを責任範囲と呼ぶが、それとModifier名を一致させるようにしよう。
修正案としては、このbg-color_red
側で設定しているwidth
は新たにsize_large
といったModifier名のクラスを作り、切り出すことだろう。
横幅ならsize
をキーとする、といった決め方をしよう。
二つ目は、これまた下のブロック内だが、今はbg-color
なので背景色だけに見えるが、実際は文字色も変更していること。
これも、今の名前では責任範囲から外れているように見える。
こちらは、例えばfont-color_white
のように切り出すよりは、そもそもこのセットを例えば警告表示で使いたいとしてtheme_caution
のようにそこまで責任範囲に含める方がスッキリするだろう。
このように、BEMでは特に色に関しては見た目ではなく意味を名前に含ませることを推奨している。
上にも書いたが、規模が大きくなるとこんな状況も発生しやすくなる。
常に、名前と責任範囲が一致しているかを確認しながら進めていこう。
なお、もちろんだが例えばfont_small
とfont_large
という二つのModifierを同時に一つのBlockやElementに付与すると、その設定が衝突するのでNGだ。
一つのModifierによる複数要素の変更
これ自体は注意点ではないが、この中に注意点が含まれている。
ここの考え方は、8つ目の拡張しやすいだ。
具体的な状況を見てみよう。
例えば、a
タグをボタンBlockとして使っており、その中にspan
タグのテキストElementを持っている状態。
<a class="button" href="...">
<span class="button__text">テキスト</span>
</a>
これについて、警告表示(theme_caution
)を表すModifierを実装したいとしよう。
このとき、これまでの説明だと以下の形で実装することになる。
<a class="button button_theme_caution" href="...">
<span class="button__text button__text_theme_caution">テキスト</span>
</a>
.button { ... }
.button__text { ... }
.button_theme_caution { ... }
.button__text_theme_caution { ... }
確かに、これでもやりたいことはできるだろう。
しかし、これだと大きく二つほど不都合が生じる。
一つ目、Blockとその中のElementにもModifierクラスを付けなければいけなく、設定し忘れの可能性が出てくる。
このままでは、BlockはつけたけどElementは付け忘れた…という時に表示が崩れてしまうのだ。
二つ目、シンプルに面倒だ。
今は単純な例で見てるのでまだいいが、数が増えてくるとそれだけ設定が大変になってしまう。
そこで、この二つのModifierが必ずセットで使われるものかを考える。
今回の場合、Blockに対するModifierで背景色を、Elementに対するModifierで文字色を変えるとしてみると、この二つは必ずセットで使われるものだろう。
片方だけだと、文字が見づらくなってしまうからだ。
こんな時は、原則の例外として子孫セレクター、あるいは子セレクタ―を使うことができる。
そのため…
<a class="button button_theme_caution" href="...">
<span class="button__text">テキスト</span>
</a>
.button { ... }
.button__text { ... }
.button_theme_caution { ... }
.button_theme_caution .button__text { ... }
こんな書き方が許容されることになる。
…のだが、ここからが注意点。
今書いたように、あくまで例外的に許可されている、ということを認識しておいてほしい。
やはり大原則は詳細度の統一なので、そこから外れたものがあまり多すぎるとそもそも破綻してしまう。
そのため、これをやる場合は必要最小限に、かつ子孫セレクター、子セレクターのネスト指定もできるだけ最小限にすること。
ちなみにだが、上の例はCSSの適用範囲を考えればbutton_theme_caution
で文字色の設定もすれば済む話だったりする。
Modifier名の省略
ちょっと参考書とは順番を変えさせてもらう。
これまでの内容を見てもらえれば分かると思うが、Modifierのクラス名は非常に長くなる。
例えば、ユーザーログインボタンBlockの中、そのテキストElementの文字サイズを大きくするModifierを考えると、クラス名は…
.user-login-button__text_font-size_large {
/* 設定する内容 */
}
こうなる。
ここまで長いと、Block名やElement名は省略してもいいのでは?と思われるかもしれないが、3つの理由から省略は避けた方がいい。
一つ目、Block名やElement名を省略すると、他のBEMエンティティに対するModifierの内容と衝突する可能性があること。
上の例でElementクラスまでを省略してfont-size_large
というクラス名にしてしまうと、他で同じく文字サイズを大きくするModifierと重複してしまう可能性がある。
そのとき、設定内容が異なるサイズの場合、設定が衝突してしまうのだ。
ここで、じゃあElementの子要素として設定すれば、と思われるかもしれないが、そうすると今度は詳細度が上がってしまう。
そのため、長くなろうと省略しない方がいい。
二つ目、どのBEMエンティティに対するModifierかが分からなくなる。
これは、後述するMixというものを使ったときに発生する問題だ。
実際にそちらでModifierを使う例も出すので、そこを見てもらえれば何が問題かは分かると思う。
その時にも少し補足をしよう。
三つ目、コードが検索しづらい。
あるBEMエンティティに対するModifierを見たいとなったとき、ここを省略してしまうと探すのだけでも一苦労だ。
省略しなければ、そのBEMエンティティで検索すればすぐ見つけることができる。
そのためにも、省略することなくクラス名をつけるようにしよう。
Blockのネスト
では、次の話題に進む。
前回もちらっと書いたが、Blockの中にBlockを入れることは可能だ。
またこのネストの深さも特に指定されておらず、Element同士のネストと同じくいくつでもネストさせることができる。
例えば、ヘッダー領域全体を一つのBlockとして見る。
その中には、ページのロゴを表すBlockや各種メニューを持つBlock、状況によっては検索領域のBlockなども入れられるということ。
今出した3つを実際にヘッダーの中へ入れてみよう。
<header class="header">
<div class="logo">...</div>
<div class="menu">...</div>
<form class="search">...</form>
</header>
さて、今はこのままでいいが、例えばロゴに着目してみよう。
別の場所では余白は不要だが、ヘッダー内でだけ余白が欲しい、といった要件を考えてみる。
今の状況のままだと、以下のように実装してしまう。
.header .logo {
/* ヘッダー内専用の余白設定 */
}
もちろんこれでは詳細度が上がってしまうので基本はNGだ。
ではどうするかというと…ここでMixという考え方が登場する。
Mix自体は次の見出しで解説するので、形だけ載せると以下のようになる。
<header class="header">
<div class="logo header__logo">...</div>
<div class="menu header__menu">...</div>
<form class="search header__search">...</form>
</header>
このように、それが「一つのBlockである」と同時に「ヘッダー内のElementである」というクラス名もつけるのだ。
Mix
今出したMixというものを見ていこう。
関連する考え方は二つだ。
- 4つ目:特定のコンテキストに依存していない
- 5つ目:詳細度がみだりに高くない
Mixとは、一つの要素に複数のBEMエンティティを付与したものだ。
こう書くとModifierと何が違うんだと思われるかもしれないが、それはMixの解説の後で見るので一旦保留する。
このMixを使う利点は、大きく以下二つ。
- ソースコードを複製せずに、複数のBEMエンティティの動作・スタイルを組み合わせられる
- すでにあるBEMエンティティから、新しいモジュールを作れる
例えば、先ほどBlockのネストで解説した例を見てみよう。
<header class="header">
<div class="logo header__logo">...</div>
<div class="menu header__menu">...</div>
<form class="search header__search">...</form>
</header>
構造としては、一つのヘッダーBlockの中に3つのBlockが入っている。
で、その中の3Blockについては、同時にヘッダーBlockに対するElementであるというクラス名も持たせている。
こうすることで、例えばこのロゴBlockには、他でも使うロゴとしてのスタイルと、ヘッダー内のElementとしてのスタイルを併せ持つことが可能になる。
具体的に、通常は余白なしだがヘッダー内ではロゴの右側に余白を付けたいとなれば…
.header__logo {
/* 余白の設定 */
}
このように、Elementとしての設定をすることで他には影響なく実現できる。
これは、見方を変えればヘッダー内専用のロゴを表す、つまり新しいモジュールを作っているとも捉えられるだろう。
また、これをしないと上でもやった通り.header .logo
という形で詳細度を上げてしまうので、それも防げるのだ。
実はもう一つ隠れた利点があり、このロゴBlockの名前が例えばblog-logo
となったとしよう。
こんな時でも、ヘッダー内の余白はheader__logo
クラス側、Elementとして付与しているので影響を受けることはない。
このように、Blockの独立性を保つこともできる。
今はBlockとElementのMixを見たが、BlockとBlock、ElementとElementのMixも可能だ。
MixとModifier
さて、状況によってはMixとModifierのどちらを使うか迷う場面もある。
上の例なら、ロゴBlockのModifierとして余白を作ることだってできるだろう。
この判断基準としては、そのスタイルが自身で完結するか、他の要素に影響を及ぼすかで考えるといい。
position
やmargin
など、他の要素にも影響を与える可能性がある場合はMix。
border
やtext-align
など、その要素自身で完結する場合はModifier。
こんな使い分けになる。
理由としては、Modifierを使う場合、やはりその責任範囲はそのBlockやElement自身に閉じているべきだからだ。
ここは、考え方一つ目の特性に応じてCSSを分類するにも繋がる。
グループセレクタ―
さて、CSSのお話だが、複数のセレクターをコンマで繋げるグループセレクタ―があった。
これを使うと、指定したセレクタ―に該当する全ての要素に対してスタイルを適用させることができる。
…こんな復習をいきなりしているのだから、もうなんとなく想像付くだろう。
このグループセレクタ―の使用を避け、代わりにMixを使うことがBEMでは推奨されている。
具体的にどう使うか、例を見てみよう。
今、ヘッダーBlockとフッターBlockには同じ設定をすると仮定しよう。
このとき、グループセレクタ―を使うと当然以下のような形になる。
<header class="header">
<!-- 省略 -->
</header>
<!-- 省略 -->
<footer class="footer">
<!-- 省略 -->
</footer>
.header,
.footer {
/* 設定する内容 */
}
これで何がまずいかというと、もし片方だけ変えたいとなったときにもう片方にまで影響してしまう。
そこで、共通の設定を行うために、新たにBlockを一つ作り出してMixでクラスを付与し、それに対して共通の設定を行う。
今回は、参考書に併せてtext
というBlockを作ってMixしよう。
<header class="header text">
<!-- 省略 -->
</header>
<!-- 省略 -->
<footer class="footer text">
<!-- 省略 -->
</footer>
これで、例えばヘッダーにのみ手を加えたいとなれば…
.text {
/* 共通で設定する内容 */
}
.header {
/* ヘッダーのみに設定する内容 */
}
これで可能となる。
…のだが、まだちょっと不安要素も残っている。
上の例では先に共通であるtext
への設定を行っているので、重複してもCSSの適用順によってheader
への設定が優先される。
しかし、順番を入れ替えるとそれもできなくなってしまう。
これはBEMでは触れられておらず、参考書で筆者の意見として二つの解決策が提示されている。
BEMの公式ドキュメントによる解説では「グループセレクターの代わりにMixを」というところで止まってしまっているのですが、筆者の意見としては
CSS設計完全ガイド ~詳細解説+実践的モジュール集
1.そもそも最初からグループセレクターを使わず、無理にMixも行わない
2.Mixをするのであれば、上書きはMixをしたBlockのModifierで行う
のいずれかを推奨します。
一つ目は、そもそもの設定が共通していようと別のブロックで定義することを表している。
上のヘッダーとフッターであれば…
.header {
/* 設定する内容 */
/* ヘッダーのみに設定する内容 */
}
.footer {
/* 設定する内容 */
}
こうなる。
二つ目は書いてある通りで、これまた上のヘッダーとフッターの場合で見てみよう。
header
の文字サイズだけ大きくしたいとすると…
<header class="header text text_font_large">
<!-- 省略 -->
</header>
<!-- 省略 -->
<footer class="footer text">
<!-- 省略 -->
</footer>
.text {
/* 共通で設定する内容 */
}
.text_font_large {
/* ヘッダーのみに設定する内容 */
}
こんな感じだ。
あとは、例えばModifierは必ずそれが修飾する対象であるBlockやElementの後に定義する、というルールを作ればいいだろう。
ちなみにだが、Modifierのクラス名を省略してはいけないところで解説した二つ目の理由はここに繋がる。
上の例で、ヘッダーのクラスにはheader
とtext
という二つのBlock名がついている。
これで、もしModifierのクラス名からBlock名を省略してfont_large
としてしまうと、このheader
とtext
どちらに対するModifierかが分からなくなってしまうのだ。
Mixでは対応できない場合
ここまでMixの使い方を色々見てきたが、これも万能というわけではなく、使えない場面というのも存在する。
Mixした二つの内容が衝突する場合だ。
例えば、Mixの中で最初に出した例を再掲しよう。
<header class="header">
<div class="logo header__logo">...</div>
<div class="menu header__menu">...</div>
<form class="search header__search">...</form>
</header>
これで、上の説明ではBlockとしてのロゴは余白なし、ヘッダー内のElementとしてのロゴは余白を作ることを考えた。
ここで、その両方に余白を別々で設定したい、という時はこのままでは衝突してしまい、定義順によって適用される内容が変わってしまう。
こんな時は、Elementの中にBlockをネストさせることで解決できる。
上の内容をこの方針で直すと、以下のようになる。
<header class="header">
<div class="header__inner">
<div class="logo">...</div>
</div>
<div class="menu header__menu">...</div>
<form class="search header__search">...</form>
</header>
これで、ヘッダー内のロゴの設定はheader__inner
側で設定することで、衝突を防ぐことができる。
WordPressに関わる注意点
一つ、メインシリーズ側のWordPressテーマ作成にも関わる注意点がある。
ブログ記事を書くとき、p
タグなんかを多量に使う。
で、そこのスタイルを設定するときは、これまでの解説だとやはりBlockかElementとして定義し、クラス名を何かしらつけて、それで対応することになるだろう。
しかし、実際に記事を書く時に毎回それを設定するのもどうだろうか。
特に、今回のように自分で使う用のテーマではなく、配布するときなんかでは、毎回クラス名を付けてもらわなければいけなくなる。
WordPressは使うだけなら正直そこまでの知識は不要で、それを要求すると書く側は非常に面倒だろう。
そこで、こういった時は子孫セレクターを使ったスタイリングが許容されている。
例えば、ブログの中身を表示するBlockをblog-post
とすると…
.blog-post p {
/* 設定する内容 */
}
これはOKだ、ということ。
ただし、これも原則である詳細度の均一から外れてしまうので、必要最小限に留めるべきだろう。
おわりに
今回はBEMの後編ということで、Modifierに関する注意点やBlock同士のネスト、Mixという考え方などを解説した。
本付録におけるBEMの解説は以上とするが、実際のBEMは前回も書いた通りCSS設計に留まらず、かなりの広範囲をカバーしている。
そのため、より深く学びたい方はやはりBEMの公式サイトを見て勉強するのがいいだろう。
さて、次回はもう一つ、PRECSSという設計手法を解説する。
まだざっとしか見ていないが、これまで解説した3つの手法をいいとこ取りしたような印象を受けている。
つまり、これまでの内容を知っておいた方がより深く理解できることだろう。
それらとの対比もあれば紹介していくので、差を確認しながら進めていこう。
2021/3/30追記
少し遅くなってしまったが、次回のPRECSS編を公開した。
これまでと違いサンプルを作り込みながら進める形なので注意してほしい。
まずはベースとレイアウトの部分をしっかり確認していこう。
オマケ:クラスの命名規則
ここからはちょっとしたオマケで、クラスの命名規則を軽く補足していく。
ここまでの内容で、BEMにおける命名規則は以下のようになっていた。
- 英単語は全て小文字
- ElementはBlockのクラス名を、Modifierはそれが修飾するBEMエンティティのクラス名を継承する
- 一つの名前に複数単語が入っていたらハイフンケース
- BlockとElementの間はアンダースコア二つ
- Modifierと修飾対象の間はアンダースコア一つ
- Modifier内のキー、値のパターンはその間もアンダースコア一つ
こんな感じだ。
これは、そのクラス名一つを見ただけでどのBEMエンティティか、ElementやModifierであれば継承している部分まで完全に把握するためのものだ。
例えば、本文中にちらっと出した以下のクラス名。
.user-login-button__text_font-size_large {
/* 設定する内容 */
}
これだけ見ても、user-login-button
というBlock、その中のtext
というElement、それに対するfont-size
をキーとしてlarge
を値とするModifierである、というのが分かる。
しかし、BEMではこの命名規則のみを推奨しているわけではなく、実際はBlock、Element、Modifierがしっかりと区別できればカスタマイズするのは問題ない。
というわけで、ここからはそのカスタマイズ例を見ていこう。
一つ目、Modifierのキー名の前後に書いていたアンダースコア一つをハイフン二つにするパターン。
.user-login-button__text--font-size--large {
/* 設定する内容 */
}
今変えた部分以外は元と同じで、これでもしっかり見分けがつく。
しかし、HTMLのコメント内にハイフン二つを入れるとバリデーションエラーになるのでそこは注意だ。
二つ目、各単語をハイフンケースではなくローワーキャメルケースにするパターン。
.userLoginButton__text_fontSize_large {
/* 設定する内容 */
}
やはりそれ以外はデフォルトから変化なし。
ちなみに、公式ドキュメントの例は間違っていると思われる。
公式の例ではBlockとElementの間がハイフン一つとなっているが、説明内に区切り文字は標準と同じと書かれていて、ここではその説明を正しいと仮定している。
三つ目、リアクトスタイルと呼ばれるもの。
Block、Element名はアッパーキャメルケース、Modifierはキー、値ともにローワーキャメルケース、BlockとElementの間をアンダースコア二つからハイフン一つに変更、という3つの手を加えたもの。
.UserLoginButton-Text_fontSize_large {
/* 設定する内容 */
}
四つ目、先に書いておくがこれはあまり推奨しない形。
Modifierに限るが、Block、Element名を省略するというもの。
ただし、Modifier名の前のアンダースコアは省略しない。
_font-size_large {
/* 設定する内容 */
}
これが良くない理由は本編中に書いているので省略しよう。
さて、公式サイトで紹介されているものはこれで以上だが、最後にMindBEMdingというものに触れておこう。
これはイギリスの会社がブログに投稿した記事名なのだが、そこから以下の命名規則を指すものとしても使われている。
- 基本は一つ目の、Modifierのキー名前後のアンダースコアをハイフン二つに変更
- Modifierのキー名は省略可能、この時の値名はハイフン二つで続ける
この二つ目に書いた、キー名省略可というのが特徴だろう。
例えば上の例のように、テキストElementにlarge
という値がついていれば、なんとなく文字サイズかなというのが分かると思う。
このように、値だけで何をしているか分かれば省略してもいい、というイメージだ。
BEM本来の命名規則ではなく、このMindBEMdingを使っているサイトも多い。
…これは私個人の意見なのだが、慣れるまではやはり大元の命名規則を使った方がいいように思っている。
ただでさえ色々と気を付けることが多いBEMなので、できる限り原則に沿った形で進めた方が混乱も減るだろう。
まずは基本を徹底し、慣れてきたらそれぞれのメリット・デメリットをしっかり理解した上で選択するようにしよう。
コメント