CSS

良いCSSとは

More than 3 years have passed since last update.


良いCSS

CSSはスタイルシート言語です。

プログラミング言語のように記述的(imperative)ではなく、宣言的(declarativ)な言語です。

では良いCSSとはどのようなものでしょうか
?

それはプログラミング言語で良いコード、クリーンコードと言われるものと同じではないかと思います。


クリーンコードは、単純で直接的です。

クリーンコードは、うまく書かれた散文のようにも読めます。

-グラディ・ブーチ


CSSはセレクタの命名とその記述方法によってのみ制御されるものです。

その制御はクリーンコードのように「うまく書かれた散文」を目指すべきです。


CSSの原則と設計

CSSはHTMLの見た目を制御する宣言が書かれた文書です。

そしてその設計にはHTMLも含まれるべきです。

CSSの仕組みは単純です。

要素を選択し、定義されたスタイル宣言を適応して見た目を変える。

原則も1つしかありません。CSSの制作チームは、この原則をカスケード(滝)と名付けました。

「選択された要素へのスタイル宣言が競合した場合、優先度によって解決される」

優先度は、書かれた順番書かれた場所重要度スタイルシート自体の優先度の5つに分けられます。


書かれた順番

文章は上から下へと解釈される為、後に書かれた宣言が優先されます。


書かれた場所

スタイル宣言は、要素に以下の様な形で直接宣言することが出来ます。

インラインで書く、インラインスタイルと呼ばれます。

HTML

<p style="display: none;">

透明段落
</p>

インラインスタイルは、スタイルシートによる宣言よりも優先されます。

また、HTMLページなどに以下のように記述されたCSSは内部スタイルシートと呼ばれます。

linkタグで、.cssファイルを読み込む場合が外部スタイルシートです。

CSS

<style>

p {
display: none;
}
</style>

内部スタイルシートと外部スタイルシートでは、優先度は変わりません。

外部スタイルシートは基本的にはHTMLのヘッダー要素で読み込まれ、内部スタイルシートは「書かれた順番」により、内部スタイルシートの宣言が優先されます。(その後に記述されることが多いためです。)

仮に内部スタイルシート、外部スタイルシートという順番で読みこめば外部シートのほうが優先されます。


重要度

スタイル宣言の末尾に!importantと付け加えることで指定できるフラグです。

インラインスタイルが宣言されていた場合でも、このフラグが付けられたスタイルが優先されます。


スタイルシート自体の優先度

ブラウザは、それぞれデフォルトのスタイルシートを適応しています。

そのため世界で最初のWebサイトにはスタイルシートはありませんがヘッドラインの文字は太く大きくなり、リンクの色が変わります。

http://info.cern.ch/

デフォルトのスタイルシートより製作者やユーザーが指定したスタイルシートが優先されます。さらに、ユーザーの作ったスタイルシートよりも製作者が優先されるのですが、感覚としてはここは逆で良いのではないかと思います。

「詳細度」というのは、読んで字のごとく「どれだけ詳しく指定されているかの度合い」です。3桁の数字で計算を行っています。

例えば、全称セレクタ(全部の要素に適応) * の詳細度は、

0, 0, 0です。

ページ上に一度しか登場しないはずのIDを指定するIDセレクタは、

1, 0, 0となります。

classセレクタは、 0, 1, 0です。

この各桁は繰り上がることはありません。また、桁の重みで計算するわけでもないので、バージョン番号に近い概念となっています。

classセレクタが入れ子の状態で宣言された場合、

0, 2, 0となります。

CSS

.header .logo {

color: red;
}

上記の例のようにCSSを見ただけでHTMLの構造も分かるように書きたい場合があります。ほとんどの場合、このスタイル宣言を.logo単体で選択した際のスタイルより優先させたいわけではありません。

わかりやすく入れ子にしたことで、意図せず詳細度が上がってしまっています。


詳細度

詳細度は、CSSにおいて最も大切な原則です。

スタイル同士が競合してしまった場合の解決方法です。

CSSにおいて大切なのは詳細度を把握しておくことでも、一定に保つことではなく、競合してしまうようなセレクタを書かないことです。

なぜでしょうか?

「CSSのセレクタは同じグローバル空間」だからと思われる方が多いかもしれません。しかし、私はこの表現はあまり好きではありません。

「セレクタの値は、一意である」と言うほうが良いと思います。

例えば、CSSは以下のセレクタが何を表しているのかを意識しません。

CSS

.header {

content: "ページのヘッダー";
}

.article .header {
content: "記事のヘッダー";
}

どんな宣言がなされていようとも、別のセレクタの入れ子になっていても、あくまでも.headerは1つのセレクタとして扱います。

その為、上記のようなコードを書いた場合は以下のようにスタイルが適応されます。(分かりやすいよう、以下ではcontentプロパティに書いた値でセレクタを呼びます。)



  1. class="header"を持つ要素にページのヘッダーのスタイルを適用する


  2. class="headerを持ち、かつ親要素がclass="article"の要素にページのヘッダーと記事のヘッダーのスタイルを当てはめる

  3. ページのヘッダー、記事のヘッダーで競合するスタイルがある場合、詳細度の高い記事のヘッダーの指定を優先する

そもそも記事のヘッダーには適応するつもりのなかった「ページのヘッダー」のスタイルが適応されてしまいます。

また詳細度が異なっていることで、同じプロパティがあった場合、値が上書きされるでしょう。

これで「記事のヘッダー」に「ページのヘッダー」のスタイルが継承されたと捉えるのは誤りです。SSで継承という場合は、プロパティに関しての話でありこの例とは関係がありません。

これは「CSSのセレクタが同じグローバル空間」であることの弊害ではなく同じセレクタ名で(CSSから見れば)全く同じもの(class="header")にスタイルを宣言しているので当然のことなのです。

上記のように、入れ子にすることで、1つのセレクタの振る舞いを変えることは、一見いいアイディアのようにも思えますが、思わぬ複雑さをもたらすことがあります。


OOCSSの功罪

OOCSSはオブジェクト・オリエンテッド・CSSの略です。

しかし私はこの考え方を全く支持できません。

この点に関しては、ほとんどベン・ダーロウ氏が書いていることと重複しますので割愛します。

http://terkel.github.io/cargo-cult-css/

CSSのセレクタは一意であり、詳細度が制御できていたとしても、

平易すぎる安易な名前を付けるべきではありません。

詳細度というのは制御できる類のものではないのです。なぜなら、「詳細度がすべてイコールの状態 = 後に書いたスタイルが優先」となり宣言の位置や読み込み順により、結局意図せず順序が決まってしまうからです。

セレクタ同士の組み合わせで、ある1つスタイルを実現する、いわゆるマルチセレクタによる設計など以ての外です。


Twitter BootstrapでのOOCSS

Twitter Bootstrap(以下Bootstrap)は便利ですが、常にあのフレームワークを真似るべきだとは思えません。

HTMLではクラス属性を複数記述することが出来ます。

Bootstrapを使ったサイトでこんなHTMLを見たことがあるはずです。

HTML

<div class="btn btn-primary btn-lg btn-block">

ただのボタンです。
</div>

各クラスをセレクタとして、スタイル郡がバラバラに宣言されています。

CSS

.btn-primary {

color: #fff;
background-color: #ee8c00;
border-color: #ee8c00;
}

.btn-group-lg>.btn, .btn-lg {
padding: 10px 24px;
border-radius: 24px;
}

.btn {
border-radius: 18px;
}

.btn, .text-ellipsis {
text-overflow: ellipsis;
overflow: hidden;
}

.btn-block {
display: block;
width: 100%;
}

Bootstrapのようにうまく設計されている場合は良いでしょう。広く知られてるためある程度予測もつきます。

しかし、それはあくまでもBootstrapでの話です。

共通するスタイルをまとめ、CSSだけを見れば見通しがよくなり、変更も容易になった感じる方もいるかもしれません。

ただし、この手法はCSSで行うべきことをHTMLで行ってるに過ぎません。

スタイルを使い回すことは、パフォーマンスやファイルサイズの低下にいかに寄与しようとも、それ以上に様々な弊害があると考えます。

プリプロセッサを使っているのであれば、共通するスタイルの共通化は、@extends@mixinで行うのが良いでしょう。

クラス属性を複数設定すること自体を否定しているわけではありません。

いわゆるユーティリティクラスと呼ばれるようなクラスを付けざるを得ない状況もあるでしょう。

しかしマルチクラスを前提として、複数のセレクタの組み合わせによって、1つの状態を表現するのはやめるべきです。

たった1つのボタンを表現するために、コード以外に以下の様なことを覚えるのは苦痛でしかありません。

ひどい自己流Bootstrapで覚えなきゃいけないかもしれない例


  • .btn.btn-primaryなどは基本的にセットで使う、displayプロパティをblockにしたい場合は.btn-blockも追加してください。


  • クラス属性の順序は.btn.btn-**を続けて書くこと。(.btn-blockなどを間に挟むとおかしくなるかもしれません。)


  • .btn-lgはbutton-largeの略ですが、ボタンが大きくするためにwidthheightではなくpaddingが変更されます。また、border-radiusの数値も変更する必要があって変更しています。(ただしクラス属性の先頭には付けないで!)


ここでも.btn.btn-primaryはCSS上でも関係があり、マルチクラスにすることで.btnのスタイルが.btn-primaryに継承される、というような認識は誤りです。それぞれ個別のセレクタに別々にスタイルを当てているに過ぎません。

どれだけクラス属性を重ねても、その要素そのものを選択するための一意の名前がないことも問題です。


BEM

BEMはOOCSSとは異なるアプローチを取りました。

BEMではDOMの構造をなぞるようなクラス属性(セレクタ名)を好みます。

html

<div class="menu">

<ul class="menu__list">
<li class="menu__list__item">メニュー1</li>
<li class="menu__list__item">メニュー2</li>
<li class="menu__list__item--checked">メニュー3</li>
</ul>
</div>

css

.menu {

}

.menu__list {
}

.menu__list__item,
.menu__list__item--checked {
}

.menu__list__item--checked {
}

HTMLとCSSのどちらを見ても、お互いの記述の検討がつきます。

記号による区切り方が特徴的なことで、他の人が見てもひと目でBEMの記法だとわかる点もポイントです。

強いて良くない点をあげれば、長いクラス名(セレクタ名)を書くことへの抵抗が薄れてしまうことです。慣れるまでハイフンとアンダーバーを重ねる記法にも抵抗がありました。


HTMLとCSSの構造の分離

「HTMLとCSSを構造的に分離しよう」という人がいます。

大抵は"HTMLを変更した際にCSSの変更がないように"くらいのものです。

ポイントは以下のとおりです。


  • 要素型セレクタを使わないこと

  • 構造擬似クラスを使わないこと

その場合は以下の様なHTML/CSSを書けません。

html

<div class="menu">

<ul>
<li>メニュー1</li>
<li>メニュー2</li>
<li>メニュー3</li>
<li>メニュー4</li>
</ul>
</div>

css

.menu {

}

.menu ul {
}

.menu ul li {
}

.menu ul li:nth-child(odd) {
}

例えばこう書きます。

html

<div class="menu">

<ul class="menu-lists">
<li class="menu-list is-odd">メニュー1</li>
<li class="menu-list">メニュー2</li>
<li class="menu-list is-odd">メニュー3</li>
<li class="menu-list">メニュー4</li>

  

css

.menu {

}

.menu-lists {
}

.menu-list {
}

.menu-list.is-odd {
}

構造の分離という名目で、ここまでやる必要があるでしょうか?

もしHTMLが変更されるのであれば、該当のCSSも変更されるのは当然です。筆者は前者の書き方をおすすめします。


ケバブケース vs スネークケース

HTMLのクラス属性についてどんな記法を用いるか議論がありますが、近年では、ケバブケースに一本化されているといって良いでしょう。

ケバブケースに至った理由は諸説ありますが、サイバーエージェントのメンテナブルCSSというページには以下の様な表記があります。


・昔のブラウザはCSSセレクタにアンダースコアを許可してなかった

・単純にアンダースコアよりもハイフンのほうが読みやすい(英語圏)

・JavaScriptの変数の命名規則と矛盾させることで、区別しやすくなる という意見があります。


Bootstrapが採用したというのも大きかったでしょう。

うろ覚えなのですが、CSS Wizardryのハリー・ロバーツ氏も早くからこの記法をおすすめしていたはずです。

理由としては「CSSのプロパティがハイフン区切りなのだから、クラス属性もハイフン統一すべき」といった主張だったと思いますが私はこの理由には首を傾げました。

なぜなら、HTMLのクラス属性と、CSSのセレクタ、ましてやプロパティは別の概念です。CSSの既存のプロパティがハイフン区切りであるならば、むしろハイフンは使うべきではないのではとも思いました。

同様の考察もあります。

CSSのセレクタ部分(IDやCLASS)にハイフンとか使われるの好きじゃないなー。ボクはあまり好きじゃないなー

BEMが登場したことで、この論争自体行われなくなっている面もあります。


IDは使わない?

ID(セレクタ)は詳細度が高すぎるので、絶対に使ってはいけないという意見があります。

しかしこれも考え方が逆ではないでしょうか。

IDセレクタは、「ページに1つしか存在しない要素を選択してスタイルを宣言しているので、他の属性よりもスタイルを優先する」のです。

IDセレクタが混乱をもたらすのは、大抵IDセレクタを親とした子孫要素を指定した場合です。

(とはいえ、前述した通り、「詳細度が高くなる=失敗」のように捉えること自体が間違いです。)

スタイルが競合しないように書かれていれば、なおさらIDを使う(スタイルを優先させたい)理由はないため、自然と使わなくなるとは思います。


よいHTML/CSSを書くために最近考えていること

散文のようにクリーンなコードを書くためにはどうすればよいでしょうか?

上記を踏まえ、最近試していることと、要点をまとめました。


  • セレクタ名はセマンティックにする

  • クラス名はHTMLの視点から命名する

  • 詳細度にとらわれ過ぎることなく、適切なネスト、適切なセレクタを用いる

  • DRYにこだわりすぎない、KISSにはこだわる

  • マルチクラス・マルチセレクタは極力使わない

  • 要素の振る舞いを変更するためにクラス属性(isHidden,.is-hidden)を付けるのではなく、WAI-ARIA(適切なものがない場合のみdata属性)を用いる

  • プリプロセッサの@import,@extends,@mixinなどの機能を使う場合、早すぎる最適化とならないよう気をつける

  • IDEに出来ることはIDEに、タスクランナーに出来ることはタスクランナーにやってもらう。

WAI-ARIAのくだりを除けば、ほとんど当たり前のことばかりです。


最後に

SassからpostCSSへの流れ、Gulpなどのタスクランナーの存在。

EDJOやCSS in JSなどの新しい考え方。様々なCSSフレームワーク。

新しい流れがありますが、個人的に一番欲しいと思っているのはCSSのツールです。IDE上で、コードを解析して、同名のセレクタや競合しているセレクタ同士を指摘してくれるようなツール。あるいはバックエンドのフレームワークとも連動して、セレクタの影響範囲となっているページやファイルを知らせてくれるツール。

そして、業界全体でコーディング規約のようなものがあれば良いと思います。もっと細かなことを言えば、.containerなのか.wrapperなのか。

この文章は現時点での筆者の個人的な考え方の総まとめをCSSのレクチャーとして書かれました。何かのお役に立てば幸いです。

*皆さん、ストックありがとうございます。公開後に校正を行っていただき、大幅に修正を行いました。(2016/3/29)