CSSはWebサービスの『ユーザー体験』を支える重要な技術で、CSSによってサービスのユーザビリティやメンテナンスコストが左右されると言っても過言ではありません。しかし、管理がずさんだったり、エンジニアがよく理解していないがために破綻しかけている現場を、筆者はこれまでいくつも見てきました。そこで、最低限これだけは知っておいて欲しいルール『詳細度と命名規則』の2点についてなるべくわかりやすく解説します。
CSSが破綻するメカニズム
現場にCSS設計(ルール)がない状態で色んな作業者が思い思いにコーディングしていくと、いつしか「指定したのに反映されない」「修正したら他の画面が崩れた」「影響範囲が読めず修正できない」という状況に陥って、本来の開発が遅々として進まなくなることがあります。
CSSの強さはランク付けされている
そもそもCSSには以下のような「詳細度」が存在し、これに基づいて優先度が決定されています。
セレクタ・指定 | SS | S | A | B | C |
---|---|---|---|---|---|
全称セレクタ(*) | 0 | 0 | 0 | 0 | 0 |
疑似要素(::before) | 0 | 0 | 0 | 0 | 1 |
要素型(div) | 0 | 0 | 0 | 0 | 1 |
属性セレクタ([type="submit"]) | 0 | 0 | 0 | 1 | 0 |
擬似クラス(:hover) | 0 | 0 | 0 | 1 | 0 |
classセレクタ(.sample) | 0 | 0 | 0 | 1 | 0 |
idセレクタ(#sample) | 0 | 0 | 1 | 0 | 0 |
インラインスタイル(style="") | 0 | 1 | 0 | 0 | 0 |
!important | 1 | 0 | 0 | 0 | 0 |
- それぞれのランクごとで計算し、上位のランクを超えることはできない(classセレクタを11個利用してもidセレクタ1個に勝てない)
- 詳細度が等しい場合は、後に記述されたものが優先される
計算例
div A=0 B=0 C=1 詳細度 = 1
ul li A=0 B=0 C=2 詳細度 = 2
ul ol+li A=0 B=0 C=3 詳細度 = 3
.sample div A=0 B=1 C=1 詳細度 = 11
.sample .item A=0 B=2 C=0 詳細度 = 20
#sample A=1 B=0 C=0 詳細度 = 100
#sample.sample A=1 B=1 C=0 詳細度 = 110
!important SS=1 詳細度 = 10000
これらを理解できていないと、CSSを反映させるために試行錯誤して時間を費やし、最終的には「ええい!important付けちゃえ!」が横行します。importantが付いた要素はその後の修正ができなくなり、WEBサービスは破綻に向かって突き進むことになります。
『BEM』を改良してみた
CSSでは「クラス名」のスコープが全てグローバルなので、クラス名同士が激しく衝突します。このため、クラス名自体の命名を工夫して衝突を防ぐ必要があるのです。(Vue.jsなどのモダンフレームワークではscoped
で擬似ローカルスコープにできますが、これも原理は同じで、フレームワークがセレクタ名を工夫することで衝突を避けています)
CSS設計(ルール)にはOOCSS、BEM、SMACSSなどたくさんありますが、一般に最も普及しているルールがBEMです。広く認知されているため、作業者のアサインがスムーズになったり、外注とのやりとりがしやすくなるといったメリットがあります。
しかし、BEMには「命名が冗長になる」というデメリットがあるため、今回はこれをより使いやすくカスタマイズして扱ってみましょう。
BEMとは
BEMは、CSSをBlock(塊)・Element(要素)・Modifier(状態)で管理する考え方です。DOMを機能や役割の「塊」ごとに分けてマークアップします。
id指定や!importantを禁止してclass指定だけで管理することで、詳細度のシガラミから解かれてシンプルに管理できるようになります。
先ず、それぞれの意味と使い方を説明します。
Block (ブロック)
- 要素の「塊」
- 機能や役割が判るように命名する ※ブロック名の命名が最重要です
- ブロック名はアッパーキャメルケースを採用する(様々な命名を表現できますし、このブロックをしっかり命名することで、サードパーティcssとのコンフリクトを防いだり、以下の命名がシンプルになります)
Element (エレメント)
- 塊の中にある各要素
- とにかくシンプルに命名する
- ブロック名でユニーク化できているので、
Block__li
くらいシンプルでもOK!
Modifier (モディファイ)
- ブロックあるいはエレメントの「違う状態」を指定する場合に使用
- 状態が判るように命名する
-
-red
のようにハイフン始まりで単独のクラスとして指定する
※ハイフンの直後に数字は禁止、必ず英字にしてください
<div class="Block">
<p class="Block__element"></p>
<p class="Block__element -modifier"></p>
</div>
このように塊の親要素であるBlockを指定し、その中にElementを指定していきます。Elementの命名はBlock__element
のようにアンダーバー2つで繋ぎます。
マークアップ例
論より実物を見てみましょう。
↑例えばこういったアイコンカードのようなパーツを作る場合、
<div class="IconCard">
<img class="IconCard__icon" src="/images/taka.png">
<p class="IconCard__name">たかたか</p>
</div>
//SCSS
.IconCard{
display: flex;
width: 200px;
padding: 10px;
border-radius: 10px;
background: #90ee90;
box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
0 0 0 2px rgb(255,255,255),
1px 1px 3px rgba(0,0,0,.3);
align-items: center;
@at-root{
.IconCard__icon{
overflow: hidden;
width: 50px;
height: 50px;
margin-right: 10px;
border-radius: 50%;
}
.IconCard__name{
font-size: 16px;
overflow: hidden;
width: 140px;
white-space: nowrap;
text-overflow: ellipsis;
color: #2c2f34;
}
}
}
このように IconCard
というブロックを用意し、その中にIconCard__icon
とIconCard__name
というエレメントを記述します。クラス名から役割や機能が推測できるように命名します。
- SCSSで
@at-root
を使っているのは、クラスの詳細度を低く抑えることでより管理しやすくするためです。 - エレメントは
&__icon
や&__name
のように省略することもできますが、.IconCard__icon
と記述した方が視認性がよく管理しやすいです。
次に下記のように「説明文」を追加する場合どのようなマークアップになるでしょう?
<div class="IconCard">
<img class="IconCard__icon" src="/images/taka.png">
<p class="IconCard__name">たかたかたかたかたかたかたかたか</p>
<div class="IconCard__description">
ホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲ
</div>
</div>
//SCSS
.IconCard{
font-size: 16px;
display: flex;
width: 200px;
padding: 10px;
border-radius: 10px;
background: #90ee90;
box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
0 0 0 2px rgb(255,255,255),
1px 1px 3px rgba(0,0,0,.3);
align-items: center;
flex-wrap: wrap;
@at-root{
.IconCard__icon{
overflow: hidden;
width: 50px;
height: 50px;
margin-right: 10px;
border-radius: 50%;
}
.IconCard__name{
font-size: 16px;
overflow: hidden;
width: 140px;
white-space: nowrap;
text-overflow: ellipsis;
color: #2c2f34;
}
.IconCard__description{
font-size: 16px;
width: 100%;
margin-top: 10px;
padding: 10px;
color: #2c2f34;
border-top: 1px solid #708090;
}
}
}
IconCard__description
エレメントが追加されましたね。
これもクラス名を見ただけで「IconCardブロックの中にある説明に関するエレメントだ」と一目で役割が判ります。
今度は背景が「黒い」バージョンを作りたい場合はどうするでしょう?
ここで登場するのが「Modifier」です。
<div class="IconCard -black">
<img class="IconCard__icon" src="/images/taka.png">
<p class="IconCard__name">たかたかたかたかたかたかたかたか</p>
<div class="IconCard__description">
ホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲホゲ
</div>
</div>
//SCSS
.IconCard{
display: flex;
width: 200px;
padding: 10px;
border-radius: 10px;
background: #90ee90;
box-shadow: inset 0 -10px 10px rgba(0,0,0,.1),
0 0 0 2px rgb(255,255,255),
1px 1px 3px rgba(0,0,0,.3);
align-items: center;
flex-wrap: wrap;
&.-black{
background: #000;
.IconCard__name {
color: #fff;
}
.IconCard__description {
color: #fff;
}
}
@at-root{
.IconCard__icon{
overflow: hidden;
width: 50px;
height: 50px;
margin-right: 10px;
border-radius: 50%;
}
.IconCard__name{
font-size: 16px;
overflow: hidden;
width: 140px;
white-space: nowrap;
text-overflow: ellipsis;
color: #2c2f34;
}
.IconCard__description{
width: 100%;
margin-top: 10px;
padding: 10px;
color: #2c2f34;
border-top: 1px solid #708090;
}
}
}
IconCard
ブロックの背景色が黒いバージョンを指定したいので、IconCard
ブロックに対して -black
クラス(Modifier)を付与します。
それに伴って IconCard
セレクタ内に下記のスタイルが追加されていますね。
&.-black{
background: #000;
.IconCard__name {
color: #fff;
}
.IconCard__description {
color: #fff;
}
}
「IconCard
に -black
クラスが付与されている場合は、背景を黒色(#000)にし、 IconCard__name
と IconCard__description
の文字色は白色(#fff)にする」という指定です。
今回は便宜的に -black
としていますが、実務では -primary
など要素の役割にもとづいて命名します。例えばサイトカラーなどをリニューアルして要素の色が青に変更された場合、 -black
などの色の名前で指定していると全ての要素を -blue
に変更する必要が出てきて今後の作業が大変になるからです。また、Modifierをアッパーキャメルケースで記述することも、ユニーク性や命名の多様性を高めるうえで有効です。
実際に触って感覚を掴んでください。Modifier(-blackクラス)を付けたり外したりするだけで、違う状態のBlockを再現できることが解るはずです。
See the Pen BEMサンプル by たかたか (@koutaka) on CodePen.
このように、サイトのパーツを役割の「塊(Block)」で分けることにより、再利用しやすく管理しやすい運用が可能となります。
現場に命名規則がなかったり、すでにあるがうまく機能していない場合は、一度これらを参考に見直してみてはいかがでしょうか。