巷で噂のBEM記法についてです。
BEMで書かれたクラスを始めて見た時に壮大な嫌悪感を感じたこともありますが、今ではなんやかんやで使いまくってるBEM。きっとそんな方が他にもいるはず。
BEMを使っているうちにセレクタのネストが嫌いになって、そのおかげでネストによるCSS優先度が引き起こす意図せぬスタイルのオーバーライドというしがらみから逃れられたり、CSSを修正したいHTMLの要素のクラスを見るだけでCSSのどこをいじればいいかが分かったりだとか、BEMの恩恵にありがたくも授かりながらも、なんでハイフンやらアンダーバーを2つもつなげなければいけないのか、他に方法はなかったのか、いや、あったはずだ!という思いを拭いきれない私は、個人的な開発ではBEMを改良して試行錯誤し、ベストな記法が見つけようという思惑にかられているのです。
ということで最近使い始めようとしている書き方をメモがてらに書いて行こうかなと。
「--」と「__」をなんとかしたい
まず、なんでハイフンやアンダーバーを2つも続けて書くのかというと、まあ単純にクラス名が複数の単語で定まる場合があって、その単語の区切りにハイフンもしくはアンダーバーが使われるからですよね。
例として
.my-profile
.first-name
.dust_box
.dodekamin_great
とかですよね。
こういう単語の区切りとしてのハイフンもしくはアンダーバーと区別するために、BEMでは2つもつなげてハイフンもしくはアンダーバーを書かなくてはなりません。
いやぁ、盛大に他に方法はあるだろうと思いますよね。単語の区切り方はそれだけじゃないぞと。我らがキャメルケースさん、お呼びですよ!
ということで、まず
(1) 単語の区切りに「-」や「_」は使わず、キャメルケースを使う
ことにしましょう。
さあ、この条件を取り入れたBEMで書いてみると
.dodekaminGreat
.dodekaminGreat-500ml
.dodekaminGreat_nutrients
になりますね。うんうん、短くなったしやや気持ち悪さが減った。
Modifierをマルチクラスに
というところで、次は完全に個人的な好みなんですが、シングルクラスやらマルチクラスやらみたいな話になります。
僕は何かアニメーションを制御する際、可能な限りはCSSのtransitionを指定しておいて、あとはクラスの切り替えによってプロパティの変更を行うことで、勝手にアニメーションが起こってくれるというようにしています。
この場合、BEM的なコードとしては
.dodekamin {
opacity: 1;
transition: opacity 500ms ease;
}
.dodekamin-fadeOut {
opacity: 0;
}
$(element).addClass('dodekamin-fadeOut');
$(element).removeClass('dodekamin-fadeOut');
になるんじゃないかと思います。
そして、Modifierを使う場面はもちろんこういう場合だけではありません。というよりむしろアニメーション専用のModifierの方がおそらくパターンは少ないと思います。
で、そういう場合は例えばこんな感じじゃないかと思います。
.dodekamin-500ml {
height: 100px;
}
.dodekamin-1L {
height: 200px;
}
そして例のマルチクラスやらシングルクラスやらの話になるわけですが、僕は今までシングルクラスで書いていました。つまり、CSSとしては
.dodekamin-500ml {
width: 50px;
height: 100px;
}
.dodekamin-1L {
width: 50px;
height: 200px;
}
みたいに、Modifierごとに共通する部分があっても同じことを書いていました。同じことを書いているといっても、実際その辺はSassを使って@extendとか使っていたので手間ではなかったのですが、結果としてはやや冗長かなぁと思うところがあり。また、先ほどのアニメーションの場合のコードを出しておいたのは、あのやり方だとマルチクラスになるんですよね。で、統一性がないなぁとも思っていまして。といってもこれはどう考えてもBEMの問題じゃなくて僕の設計の問題なんですけれども。
という、以上の問題を解決すべく、僕が導きだした解決策は、マルチクラスでModifierを書いていくというもの。アニメーションにしろ500ml/1Lの違いにしろ、これからは次のように書こうと思います。
.dodekamin {
width: 50px;
opacity: 1;
transition: opacity 500ms ease;
}
.dodekamin.cap500ml {
height: 100px;
}
.dodekamin.cap1L {
height: 200px;
}
.dodekamin.fadeOut {
opacity: 0;
}
(クラス名は数字から始めることができないため、500ml/1Lの前にcapacityの意味でcapをつけました。)
こうすると、マルチクラスに統一できる上に、アニメーションにおいて
$(element).addClass('dodekamin-fadeOut');
-> $(element).addClass('fadeOut');
と、記述量が短くなるというおまけつき。ありがたや!
かつ、JSによるスタイル切り替えが全部同じようにできてよい。シングルクラスになぜかこだわってたけどこの辺マルチクラスの方が柔軟な気がしてよい。
というわけで
(2) Modifierには「--」は使わず、マルチクラスで表現する
ようにします。
また、
(3) 「-」「_」「--」は使わなくて済むので、Elementを示すには見慣れない「__」ではなく一番自分の見慣れた「-」を使う
ことにもします。
Blockを分かりやすくする
というわけで、最後はおまけとして、
<div class="hoge fuga piyo"></div>
なんてのがあったとして、マルチクラスでModifierを書いたら、hogeとfugaとpiyoのどれがBlockでどれがModifierなのか分からなくなります。
ということで、こうしたら分かりやすいんじゃないでしょうか。
<div class="Hoge fuga Piyo"></div>
これは単純で、Blockの最初の文字を大文字にして、そのBlockに対するModifierは必ずその後に書いていくというもの。その上で上記のコードを見たとき、「HogeというBlockに対してfugaというModifierがついている。そしてPiyoというBlockのCSSも適用される」というのが分かると思います。まあBlockが2つ同時に指定されるという状況がBEM的に存在するのかどうかは謎ですけれども。
ということで
(4) マルチクラスで表現されるModifierとBlockと区別するため、Blockの最初の文字は大文字
にします。
まとめると
- 単語の区切りに「-」や「_」は使わず、キャメルケースを使う
- Modifierには「--」は使わず、マルチクラスで表現する
- 上記2点より「-」「_」「--」は使わなくて済むので、Elementを示すには見慣れない「__」ではなく一番自分の見慣れた「-」を使う
- マルチクラスで表現されるModifierとBlockと区別するため、Blockの最初の文字は大文字
ですね。といってもこれを導入して何かやったことがまだあまりないので、改良案が見つかれば改良していきます。
最後にこれらを適用したBEMで何かクラスを書いてみるとこんな感じになります。毎回どうも例が適当ですみませんでした!(今回も全然いい例が思いつきませんでした…)
また、この例だと冒頭で嫌いだといったセレクタのネストがありますが、あくまで嫌いだった原因はネストによって意図しないスタイルのオーバーライドが起きることであり、こうやって書いていくと意図するところだけでオーバーライドが起きてくれて、むしろ!importantなんかを使う機会が個人的には減っているのでまあいいかなという感じです。
.Android {
font-family: "Droid Sans";
}
.Android.kitkat {
content: "Version 4.4";
}
.Android.jellyBean {
content: "Version 4.1-";
}
.Android-cyanogenMod {
}
.Android.kitkat .Android-cyanogenMod {
content: "Version 11";
}
.Android.jellyBean .Android-cyanogenMod {
content: "Version 10-";
}