CSS Subclassing

上野 学(@manabuueno)- 2014年1月17日

CSS Subclassing は、主にアプリケーションよりのウェブの実装に役立つ CSS の書き方である。

アプリケーションよりのウェブとは、企業広報サイトのように各画面でデザインの個別性が高いものではなく、コンポーネント化されたUI要素の組み合わせのみで各画面が構成されるようなもののことである。

CSS Subclassing の目的は、HTML 要素をユーザーインターフェースオブジェクトのインスタンスとして捉え、そのスタイルをクラスとして定義することにある。そしてあるクラスの性質(スタイル)を継承した、サブクラスの定義もできるようにする。

HTML 要素にスタイルをあてる場合、class 属性にクラス名をセットしてそれを CSS のセレクターに用いることは普通に行われる。しかし HTML 要素の class 属性は、いわゆるオブジェクト指向プログラミングにおけるクラスと違って、サブクラスを作ることができない。だからこれは言ってみればただのタギングにすぎない。

しかしウェブアプリケーションの多くは JavaScript の名前空間においてUI要素のクラスをコンストラクタおよびそのプロトタイプとして定義しているのだから、そこから生成される DOM 要素のスタイルも同じくオブジェクト指向をもって定義されるべきである。

このような考え方でスタイルを扱うのは、すべての DOM 要素について JavaScript から style 属性を直接セットすることで可能になるかもしれないが、これはフロントエンド実装の作法としてはあまりよしとされないだろう。やはりスタイルは CSS としてプログラムから分離させたい。

CSS を構造化して、あるセレクタに定義されたスタイルを別のセレクタに継承(インクルード)させるという方法は、昨今、CSS プリプロセッサを用いて一般的に行われている。しかし CSS プリプロセッサが構造化してくれるのはあくまで CSS を書く作業についてであって、出力される CSS ファイルはむしろ非構造的で冗長なものになるという問題がある。

CSS Subclassing の手法ではこの問題を解決し、プリプロセッサを用いない単純な方法で、CSS を構造化し、HTML の class 属性をオブジェクト指向プログラミングのクラスに近い形で取り扱おうとするものである。

CSS Subclassing は以下の5つのルールによって実現する。

  1. セレクタにはできるだけ class を使う。
  2. セレクタはできるだけ短くする。
  3. クラス名にはUI要素としての名前を付ける
  4. UI要素のバリエーションは、スーパークラス - サブクラスの定義によって実現する。
  5. JavaScript による class 属性の変更でスタイルを変化させる場合は、サブクラス名の追加で実現する。
1, 2, 3 および 5 については比較的一般的になっていることかもしれない。重要なのは 4 である。4 の方法についても、Bootstrap などでは似たような記法が部分的に用いられているが、ここで示すような明確なルールにはなっていない。

1. セレクタにはできるだけ class を使う。

Discussion

CSS はクラス定義であると位置づけ、たとえページ中にひとつしか存在しない要素であっても class を用いてセレクトする。id は JavaScript が要素を特定するために用い、セレクタには使わない。

中には明らかにひとつのインスタンスに特有のスタイルもあるが、それについてはむしろ HTML 内の style 属性で直接指定する。

HTML 全体に適用することを目的としたタグセレクタは用いてもよい。

クラス名にはハイフンやアンダースコアは使わず、キャメルケースで書く。その理由は 4 を有効にするためである。

Example

#mainBlock { width: 980px; } /* NG */
.mainBlock { width: 980px; } /* OK */
ul { list-style-type: none; } /* OK */

2. セレクタはできるだけ短くする。

Discussion

文脈セレクタはできるだけ使わない。使う場合でも、タグ名は含めない。

これにより、スタイルが HTML 構造やタグのセマンティクスに紐づかないようにする。親要素が変わったり、タグが変わっても、できるだけスタイルが影響を受けないようにする。

Example

p.main { color: red; } /* NG */
.main { color: red; } /* OK */
div.main table tr td div.cell { color: red; } /* NG */
.cell { color: red; } /* OK */
.classA .classB .classC { color: red; } /* NG - too complicated */
.classA .classC { color: red; } /* OK */
ul.list li { color: red; } /* NG */
.list > * { color: red; } /* OK */

3. クラス名にはUI要素としての名前を付ける。

Discussion

CSS はUI要素のクラス定義であると位置づけ、クラス名にはそのUI要素がシステム中の不特定箇所で再利用されることを前提とした名前を付ける。

セレクタは、HTML 中のある要素を特定するためのものではなく、あるUI要素のクラスを示すものと考える。そのため、スタイルの内容を表したようなクラス名や、スタイルを細分化してタギングのように扱う目的のクラス名は用いない。

Example

.mediumFontSize { font-size: 16px; } /* NG */
.blueButton { background-color: blue; } /* NG */
.backgroundColorBlue { background-color: blue; } /* NG */
.mainButton { font-size: 16px; background-color: blue; } /* OK */

4. UI要素のバリエーションは、スーパークラス - サブクラスの定義によって実現する。

Discussion

共通の性質を多く持つが細部が異なるUI要素がある場合、基本的な性質をスーパークラスとして定義し、そのバリエーションをサブクラスとして定義する。しかし HTML の class 属性には、クラス/サブクラスの考え方はない。そのため次の方法でスーパークラスとサブクラスの関係を作る。

CSS ファイルにおいて、まず、スーパークラスとしてのクラス定義を書く。

そしてその定義よりも後に、サブクラスとしてのクラス定義を書く。その際、スーパークラスとの差分のみを書いてオーバーライドする。

サブクラスのクラス名は、そのスーパークラスが何であるか分かるよう、スーパークラス名の後ろにハイフン繋ぎで文字列を追加した形にする。

例えばスーパークラスとして "super" というクラスを定義し、そのサブクラスとして "super-sub" というクラスを定義する。

HTML 内では、class 属性として、「class="super super-sub"」のように、スーパークラス名とサブクラス名の両方を含めて記述する。

ポイントとしては、単に複数のクラスづけによって断片的なスタイルを組み合わせるのではなく、きちんとUIのクラスとして意味を持った単位でスタイルが定義/継承され、なおかつ HTML の class 属性をみればその要素がUIとしてどのようなクラス階層に位置づけられているのかが分かること。

当然、UI要素としてのクラス単位で後からスタイルを修正できるので、あるスタイルの変更が及ぼす影響範囲をかなり正確にコントロールできる

Example 1

- CSS -

/* Super class */
.button {
	width: 80px;
	font-size: 14px;
	background: #eee;
	border: 1px solid #999;
}

/* Sub class of button */
.button-submit {
	border: 2px solid #39f;
}

/* Sub class of button-submit */
.button-submit-special {
	background-color: #9cf;
}

- HTML -

<button class="button">Cancel</button>
<button class="button button-submit">OK</button>
<button class="button button-submit button-submit-special">Boom</button>

- View -

Example 2

- CSS -

.menu {
	position: relative;
	background: #66f;
	margin: 0;
	padding: 0;
}
.menuItem {
	display: table-cell;
	border-right: 1px solid #99f;
}
.menuItem-status {
	position: absolute;
	top: 0;
	right: 0;
	border-left: 1px solid #99f;
	border-right: none;
}
.menuAnchor {
	display: block;
	color: white;
	line-height: 44px;
	padding: 0 10px;
	text-decoration: none;
}
.menuItem-selected .menuAnchor {
	background-color: #33c;
}
.menuAnchor:active {
	background-color: #009;
}

- HTML -

<ul class="menu">
	<li class="menuItem menuItem-selected">
		<a class="menuAnchor" href="javascript:void(0)">Item 1</a>
	</li>
	<li class="menuItem">
		<a class="menuAnchor" href="javascript:void(0)">Item 2</a>
	</li>
	<li class="menuItem">
		<a class="menuAnchor" href="javascript:void(0)">Item 3</a>
	</li>
	<li class="menuItem menuItem-status">
		<a class="menuAnchor" href="javascript:void(0)">Item 4</a>
	</li>
</ul>

- View -

5. JavaScript による class 属性の変更でスタイルを変化させる場合は、サブクラス名の追加で実現する。

Discussion

マウスイベントなどによってある要素のスタイルを一時的に変化させたい場合、あらかじめ変化の前と後のスタイルを CSS で定義しておき、JavaScript で class 属性の値を動的に変更することで実現する。その際、class 属性の変更の仕方は、クラス名の差し替えではなく、サブクラス名の追加で行う。

Example

ある DIV 要素の背景色を一時的に灰色から黄色に変化させたい場合は次のようにする。

- CSS -

.box {
	padding: 10px;
	border: 1px solid gray;
	background-color: #eee;
}
.box-selected {
	background-color: #ff3;
}

- HTML 変化前 -

<div class="box">Click Me</div>

- HTML 変化後 -

<div class="box box-selected">Click Me</div>

- View -

Click Me

次のようにするのは NG 。

- CSS -

.normalBox {
	padding: 10px;
	border: 1px solid gray;
	background-color: #eee;
}
.selectedBox {
	padding: 10px;
	border: 1px solid gray;
	background-color: #ff3;
}

- HTML 変化前 -

<div class="normalBox">Click Me</div>

- HTML 変化後 -

<div class="selectedBox">Click Me</div>

補足

ひとつの HTML 要素に複数のクラスを付けることを前提に、あらかじめ

.large { font-size: 3em }
.red { color: red }

のようにスタイルの断片を定義しておいて、HTML の中で

<p class="large red">

のようにそれを組み合わせて使うという方法を、私はクラスタギングと呼んでる。このやり方では、個々のセレクターを完全に独立したものとみなして、ある HTML 要素に自由にタグ付けしていくような感覚で目的のスタイルを実現する。

こういった書き方を OOCSS と呼ぶ人もあるようだが、単なるタギングをオブジェクト指向と言ってよいかどうか疑問なので、私はクラスタギングと呼ぶ。

クラスタギングは、スタイルの定義を HTML 構造に依存させない(セレクタにタグ名を含めない)という意味ではよいが、問題もある。

ひとつは、デザインの作業が、スタイルシート上ではなく、HTML 上で行われるという点。

クラスタギングの方法を突き詰めると、あらゆるスタイル断片に名前を付けてあらかじめ定義しておき、HTML 要素のクラス属性に求めるクラス名を列記することになる。つまり結局、スタイルの定義を HTML 上でしていることになり、ドキュメントとスタイルの分離という大義に反してしまう。

ある要素のスタイルを後から変更したい時、スタイルシートではなく、HTML を修正しなければいけない。

これを良しとするなら、わざわざスタイル断片を定義しておかなくても、HTML 要素の style 属性に直接スタイルを書けばいいということになる。

同じ理由で、クラス名にスタイルの内容を表す文言を含める(例:.button_large とか .menu-left など)のはNGなのである。

もうひとつは、スタイル定義を変更した際に、影響範囲が予測できないという点。

あらかじめ定義しておいたスタイルの断片をクラス名として HTML 上で好きに組み合わせていると、あるクラス名がシステム全体のいろいろな箇所で脈絡なくばらばらに用いられることになる。

するとあるセレクターに対するスタイル定義を後から変えた場合に、それがシステム内のどの箇所に影響するかを事前に予測しづらいので、本来変わってほしくない箇所にまでそれが適用されてしまう恐れがある。

そういうことが起きると、結局 HTML 内のクラス属性値を書き換えなければいけなくなる。

この問題は、クラス名と、UI要素の役割との間に、意味的な関連がつけられていないことが原因だ。

クラス名がUI要素の役割(つまりクラス)を指す名称になっていて(例:.button-primary とか .menu-local など)、それをセレクターにしてスタイルが定義されていれば、スタイル定義を変更した時の影響範囲は自明となる。もしそれでも意図しない結果になるのだったら、UI設計として要素の構造化がうまくできていないということである。

CSS Subclassing の記述法は、これらの問題を解決できる。

CSS Subclassing の課題

CSS Subclassing の課題のひとつは、HTML 要素のクラス属性値が長くなることである。

CSS Subclassing では、クラス属性値でそのUIのクラス階層をすべて書かなければいけないので(例:class="super super-sub super-sub-subsub")、あるクラスのサブクラスのサブクラス、のようにクラス階層が深いと文字量が多くなる。

ただしその分、CSS の記述は構造化されて(プリプロセッサが出力するものと比較すれば)少なく済む。

UI設計がきちんとできていて、HTML においてそれぞれのUI要素の役割(クラス階層)が適切にクラス属性として記述されていれば、後からスタイルを変更したい場合に HTML を修正する必要はなく、CSS だけを書き換えればよいということになる。

もうひとつの課題は、クラス属性値の記述順序を厳密にしなければならないこと。

要は、サブクラス名よりも前に、スーパークラス名が書かれていなければならない。

こういう CSS がある時、

.super { color: green; background: blue }
.super-sub { color: red; }

これはNG

<p class="super-sub super">

こうしないといけない

<p class="super super-sub">

ただしこれは、例えばプログラミングで、サブクラスの定義をロードするよりも前にスーパークラスの定義がロードされていないといけないのと同じで、当たり前といえばあたり前だろう。

最後に、一番本質的なことだが、CSS Subclassing の記法は、UIが構造的に設計されていることを前提としている点である。

UI要素の役割がクラス階層として捉えられていて、そのクラス階層に対してスタイルを定義していくという考え方なので、画面の中の要素ひとつひとつについて、システム全体の中での共通性と個別性をかなり細かく設定しておかなければいけない。

だから、ページ毎の個別性が高いウェブサイトでは、CSS Subclassing を使う意味があまりないのである。