破綻しにくい CSS 設計手法と命名規則

公開日:
更新日:
閲覧数:4,737タグ:Webデザイン

CSS が破綻する理由

CSS は以下のようなシンプルなフォーマットで構成されます。

selector{
  property: value;
}
CSS のフォーマット

HTML 側の内容とセレクタがマッチすればスタイルが適用されます。 このように CSS はとても単純な仕組みですが、その単純さゆえに大規模な実装では管理が難しく簡単に破綻してしまう問題点を抱えています。

CSS が破綻してしまう主な理由は以下の通りです。

開発現場では CSS が単純であるがために軽視されてしまう嫌いがあります。 多くのプロジェクトでは Java や SQL などのコーディング規約やアンチパターンなどは用意されていますが、CSS は用意されていないケースは珍しくありません。 その結果、統一されたルールがない状態でプロジェクトが進み、開発が佳境にさしかかったタイミングで CSS の逆襲が始まります。 CSS が悪いわけではありません。 CSS を軽んじた開発方針が悪いのです。 それでは、CSS が破綻する理由を順番に確認していきましょう。

セレクタに詳細度がある問題

CSS には "詳細度" (優先順位) が存在するため、詳細度がもっとも高いスタイルが優先されます。

/* CSS側 */
#foo{ color: red; }
.bar{ color: blue; }

<!-- HTML側 -->
<p id="foo" class="bar">
ここの文字は何色でしょう?
</p>
セレクタの詳細度

上記の例では、#foo が優先されるため文字は赤色になります。 各セレクタの詳細度が整理されていない場合、より詳細度の高い定義が優先され、詳細度の低い定義が上書きされます。 正しい設計としてはスタイルの上書きをしないことが望ましいのですが、適用済みのスタイルを変更することで他の要素にも影響がでるため、仕方なくスタイルの上書きをすることがあります。 このように上書きを何回も重ねることによって、古いスタイルが変更できなくなり破綻することになります。

スタイルが子要素に継承される問題

スタイルには HTML 側の子要素にまで継承するため、DOM (Document Object Model) ツリーの上位の要素にスタイルを適用すると、下位の要素で上書きを余儀なくされ破綻してしまいます。

/* CSS側 */
body{ font-size: 16px; }
section p{ font-size: 12px; }

<!-- HTML側 -->
<section>
  <p>すべての文字が16pxになるので12pxで上書きしなきゃ...</p>
</section>
スタイルが子要素まで適用される

上位の要素に多くのスタイルが適用されている場合、下位の要素になるほど多くの影響を受けます。 また、DOM ツリーの構成が整理されていない場合、継承されるスタイルが一意に決まらないため、多くのスタイルを上書きすることになります。

同じセレクタを多重定義できる問題

CSS の仕様では、同じセレクタを多重定義してもエラーになりません。

/* CSS側 */
.bkcolor{ background-color: black; }
.bkcolor{ background-color: white; }
同じセレクタの多重定義

CSS の定義が多くなると、同じセレクタを多重定義してしまう場合があります。 多重定義しやすいケースとしては、別ファイルで同じセレクタを多重定義してしまう場合や、行数が多い CSS ファイルの中で多重定義してしまうなどです。 セレクタが汎用的な名前であれば、多重定義のリスクは高まります。

スタイルが複合的に適用される問題

HTML の各要素はスタイルを複合的に受け入れます。 スタイルが合成された場合、最終的にどのようなスタイルになるのか予測は困難になります。

/* CSS側 */
.cls1{ font-size: 16px; }
.cls2{ font-color: red; }
.cls3{ font-weight: bold; }

<!-- HTML側 -->
<p class="cla1 cla2 cla3">スタイルが合成された文字</p>

スタイルが複合的に適用される

スタイルを複合的に適用する場合、各スタイルを厳密に管理しなければ組み合わせをコントロールできません。

破綻しにくい CSS 設計とは

大規模なプロジェクトでは CSS が簡単に破綻してしまうため、破綻しにくい CSS 設計手法は昔から求められていました。 破綻しにくい CSS 設計で求められる要件としては、以下があります。

上記の要件を満たす設計手法として、いくつもの方法が考案されています。 その中でも有名な設計手法としては、"OOCSS""BEM""SMACSS""FLOCSS" があります。

OOCSS

OOCSS とは、"Object Oriented CSS" の略です。 OOCSS は、Nicole Sullivan が提唱したもので CSS にオブジェクト指向の考え方を取り入れてます。

OOCSS では、ページを構成するパーツをレゴブロックの集まりであると考えます。 ページを作るには、レゴのパーツをどんどん積んでいくように考えるのです。 そして、各パーツはパーツ単位で完結するように CSS を定義すると破綻しにくい構成になります。

例えば、ページの中に複数の h2 要素があったとします。 OOCSS では、それらの各要素に専用のスタイルを定義します。

/* CSS側 */
h2{ font-size: 16px; }
.h2_r{ font-color: red; }
.h2_b{ font-color: blue; }
.h2_w{ font-color: white; }

<!-- HTML側 -->
<h2 class="h2_r">Title Red</h2>
<h2 class="h2_b">Title Blue</h2>
<h2 class="h2_w">Title White</h2>

各要素に専用のスタイルを定義する

OOCSS では、各パーツを "CSS オブジェクト" と呼んでいます。 CSS オブジェクトを使いまわすことで余計なコーディングが減り、スタイルの上書きもなくなります。

OOCSS では "場所に依存するセレクタの定義" は、アンチパターンとして扱われます。

h2{ ... }
#main h2{ ... }
#main .header h2{ ... }
#main .footer h2{ ... }

場所に依存するセレクタ定義

上記の例では、h2 要素がすべて場所に依存しています。 OOCSS では、このような設計ではいずれ破綻すると考えるため、アンチパターンとしています。 なぜなら、ページ内に要素が複数ある場合、以下のように部分的な上書き合戦になってしまうためです。

h2{
  color: red;
  margin: 10px;
  padding: 10px;
}
#main h2{
  color: black;
  margin-top: -20px;
}
#main .header h2{ padding: 0px; }
#main .footer h2{ padding: 0px; }

スタイルを部分的に上書きする例

新しく使おうとする要素が、すでに何らかのスタイルが指定されているかもしれません。 スタイルが指定されていた場合、上書きによって打ち消すしかありません。 スタイルの上書きは無駄である上に、管理上のリスクになります。 このような上書きが積み重なっていき、CSS は複雑化していきます。

このような上書き合戦の状態が続き、打ち消すことができない !important が登場すると終焉が近いことを示しています。 もしも !important を指定したスタイルを修正したいと思っても、簡単に修正するわけにはいきません。 なぜなら、すでに別のどこかの要素にスタイルが使われている可能性があるため、修正後の影響を把握できないからです。

また、他の誰かによって、あたなの定義したスタイルが上書きされる可能性もあります。 そのため、あなたは自分のスタイルが上書きされないように !important を指定することしかできません。 !important はウィルスのように広がり、CSS ファイルは !important だらけになります。

構造と見た目の分離

OOCSS では、"構造と見た目を分離する" という重要な考え方があります。 構造と見た目を分離していない CSS の例としては以下のようなものがあります。

/* CSS側 */
#boxA{
  width: 50px;
  height: 50px;
  border: 1px solid #333;
  background-color: black;
}
#boxB{
  width: 50px;
  height: 50px;
  border: 1px solid #ccc;
  background-color: white;
}

<!-- HTML側 -->
<div id="boxA"></div>
<div id="boxB"></div>

構造と見た目を分離していない CSS の例

上記の例では OOCSS として 3 つの NG があります。

  • id を使うとスタイルの使い回しができない
  • 構造を定義するスタイルが重複している
  • CSS が煩雑になりメンテナンス性とパフォーマンスが下がる

上記の例を、構造と見た目を分離した例で書き直すと以下のようになります。

/* CSS側 */
.box{
  width: 50px;
  height: 50px;
}
.box-black{
  border: 1px solid #333;
  background-color: black;
}
.box-white{
  border: 1px solid #ccc;
  background-color: white;
}

<!-- HTML側 -->
<div class="box box-black"></div>
<div class="box box-white"></div>

構造と見た目を分離している CSS の例

上記のように構造と見た目を分離することに成功できれば、メンテナンス性とパフォーマンスの向上が見込めます。 しかし、簡単そうに見えても正しく OOCSS で設計を行う場合、高度な設計技術が求められます。

OOCSS の設計を難しくしている原因は、"構造" と "見た目" をどのように分離するか明確にしなければならない点です。 例えば、widthheight は構造に分類されますが、border はどちらに分類されるでしょうか? 厳密には border は構造と見た目をまとめて定義できるため、どちらにも分類することはできません。 細分化されたプロパティとして border-width は構造に分類され、border-color は見た目に分類されます。 構造と見た目をまとめて定義できるプロパティを OOCSS で扱う場合には注意が必要です。

OOCSS で設計を行う上で、もうひとつ重要な点があります。 それは OOCSS の設計の前に、サイト全体の設計を行い、構造と見た目のパターンを決めておくことです。 つまり、サイトを組み立てる前に、必要な "レゴブロック" を洗い出して構造と見た目をすべて整理しておきます。 組み立てる作業はレゴブロックを揃えてからになります。 組み立てながら必要なレゴブロックを定義する方法では、手戻りの発生や今後のメンテナンスに重大な影響を与える可能性があります。

BEM

BEM とは "Block""Element""Modifier" の頭文字を取った略語です。 CSS を扱う上での BEM と言えば、一般的には HTML を構成する要素に対するセレクタの命名規則を表す "MindBEMding" を意味します。

BEM では element を "__" 、modifier を "--" で連結することで各要素を表現します。 ハイフンやアンダースコアが 1 つではなく 2 つである理由は、クラス名をブロック単位に区切るセパレータと区別するためです。 つまり、ハイフンやアンダースコアが 1 つであれば、それは BEM の連結記号ではなく名前の区切るために表現している記号であることを示しています。

block
block__element
block__element--modifier
block--modifier
block--modifier__element

/* BEM の例 */
.site-search        /* block    */
.site-search__field /* element  */
.site-search--full  /* modifier */

BEM の表記方法

BEM の表記方法は、プログラミングの経験が長い人ほど拒絶反応を起こすレベルで醜いものです。 しかし、美しさを犠牲にすれば BEM の命名規則は分かりやすく、使いやすいことがわかります。 BEM を使った HTML は以下のようになります。

<div class="block">
    <div class="block__element"></div>
    <div class="block__element--modifier"></div>
</div>
<div class="block--modifier">
    <div class="block--modifier__element"></div>
</div>
BEM を使ったクラス名の例

BEM では block がすべてのルートとなり、その中に element が存在します。 modifierblock または element が変化して派生した状態の要素を表します。

BEM は CSS のクラス名に高い透明性を与え、他の開発者にクラス名の意味を伝えやすくします。 そのため、大規模なプロジェクトに適した命名規則であると言えます。 BEM の命名規則がプロジェクトで使われている場合、HTML の要素と付与されているクラス名から、他のコンポーネントとの関係性や、どのコンポーネントから派生したものなのか判断できます。 これらの利点は地味ではありますが、プロジェクトに規律と秩序をもたらし、品質やコスト面においても重大な役割を果たします。

BEM はコードが美しくないという点において多くの反論があります。 確かに BEM の連結記号は美しくありませんが、見た目を理由に BEM を避けようとしているのであれば、それは本質を見失っています。 BEM は見た目さえ我慢できれば、それを上回る恩恵を受けることができます。

SMACSS

SMACSS とは、"Scalable and Modular Architecture for CSS" の略で、"スマックス" と読みます。 SMACSS は CSS を 5 種類のルールにカテゴライズして記述する手法です。 5 種類のルールとは、"ベース""レイアウト""モジュール""ステート""テーマ" です。

SMACSS では、HTML と コンテンツのセマンティックな価値を向上させ、特定の HTML 構造への依存を低減することをゴールと定めています。

ベースルール

ベースルールは、要素のデフォルトとなるスタイルを定義します。 ベースルールの最大の特徴は、要素に対してスタイルを定義するため、id や class を使用しないことです。

body {
  background: white;
}
a:link {
  color: blue;
}
ベースルールの設定例

ベースルールを定義する上で注意しなければならないことは、各要素に対して多くのスタイルを定義しないことです。 なぜなら、多くのスタイルを定義してしまうと、後から追加するモジュールに対してスタイルを上書きするリスクを上げてしまうためです。

ベースルールは各要素の構造や見た目を整えるため "normalize.css" や "reset.css" と本質的には同じです。 各要素を初期化する CSS とベースルールを併用する場合は、重複定義に注意しましょう。

各要素を初期化する CSS には全称セレクタが使われていたり、使われていない要素への定義が含まれていたりします。 その結果、予期しないスタイルが適用される可能性があるため、安易にベースルールと併用するべきではありません。

レイアウトルール

レイアウトルールは、ページを各セクションのエリアに分けるための定義を行います。

.layout-header{
  margin-top: 20px;
}
.layout-main{
  width: 80%;
}
.layout-sub{
  width: 20%;
}
.layout-footer{
  margin-bottom: 20px;
}
レイアウトルールの設定例

ページを分けるセクションとしては、ヘッダー、メイン、フッターが代表的です。 その他にもサブメニュー、ナビゲーション、各パーツを配置するエリアなどがあります。

レイアウトルールをクラス名で管理する場合、"layout-" や "l-" などのプレフィックスを付けると明確に区別できます。 "layout-" や "l-" のプレフィックスは公式で紹介されている方法ですが、どのような文字列でも構いません。 特に大規模なプロジェクトではコーディング規約に命名規則が定められている場合が多いため、プロジェクトのルールに従った名前にしましょう。

また、レイアウトルールの相対幅、固定幅の管理方法として子孫セレクタを使って分岐させる方法もあります。 "fixed" がクラス名についている場合は px 指定、そうでない場合は % 指定となります。

.layout-main {
  width: 80%;
}
.layout-fixed .layout-main {
  width: 600px;
}
子孫セレクタで相対幅と固定幅を分岐する設定例

ヘッダーやフッターなどの主要なレイアウトでは、慣習的に id セレクタが使われます。 しかし、SMACSS では class セレクタが使われており、id セレクタは使用していません。 id セレクタの使用が禁止されているわけではありませんが、id セレクタを使う場合、以下の点に注意する必要があります。

  • id セレクタは CSS の詳細度を高めるため、複雑化するリスクを招きやすい
  • id セレクタを利用すると、スタイルの上書きが難しくなる
  • id セレクタは同一ページで複数の要素に指定できないため、柔軟性を失う

モジュールルール

モジュールルールは、ロゴ、ナビゲーション、タブなどのページを構成するすべてのパーツに対してスタイルを定義するルールです。 各パーツはページのどこに配置しても再利用できるようにスタイルを独立させておく必要があります。 スタイルを独立させるための命名規則として、親モジュールの名前をプレフィックスとして利用します。

<div class="item">
  <p class="item-text"></p>
</div>
モジュールルールの命名規則

ただし、同じ要素であっても配置場所が異なる場合、親要素による分岐を行うべきではありません。 なぜなら、親要素による分岐を行った場合、配置場所が変わるとスタイルが崩れるためです。

<!-- NG -->
<div class="layout-main">
  <p class="item"></p>
</div>

<div class="layout-sub">
  <p class="item"></p>
</div>


<!-- OK -->
<div class="layout-main">
  <p class="item"></p>
</div>

<div class="layout-sub">
  <p class="item item-red"></p>
</div>
親要素を分岐条件として利用しない

ステートルール

ステートルールは、特定の状態によってスタイルを上書きするルールです。 通常の状態では使用されず、javascript などで状態の切り替えが発生した場合に用いられます。 状態の切り替えとは、モジュールの表示・非表示、正常・異常、オン・オフなどを指します。

.is-disable{
  ...
}
.is-current{
  ...
}
.is-active{
  ...
}
ステートルールの命名規則

ステートルールでは、プレフィックスとして "is-" が使われます。 ステートルールのクラス名には、固有のモジュール名を付与するとスタイルの競合を避けることができます。

テーマルール

テーマルールは、見た目に関するスタイルを定義するルールです。 プレフィックスとして "theme-" が使われます。

.theme-font {
  color: #333;
}
.theme-background {
  background-color: #fff;
}
テーマルールの命名規則

FLOCSS

FLOCSS (フロックス) は OOCSS、BEM、SMACSS、SuitCSS、MCSS の考え方を取り入れた設計手法です。 命名規則は MindBEMding を採用し、"Foundation""Layout""Object" の 3 つのレイヤーで構成されています。 Object レイヤーは、"Component""Project""Utility" の 3 つの子レイヤーを持ちます。

Foundation レイヤー

Foundation レイヤーは reset.css や normalize.css などのスタイルの初期化や、SMACSS のベースルールで扱うデフォルトのスタイルを定義します。 ページの背景や、フォントの設定などを Foundation レイヤーで定義します。

Layout レイヤー

Layout レイヤーはページを構成するヘッダーやフッター、メインコンテンツ、サイドバーなど共通で使用されるエリアのスタイルを定義します。 これは SMACSS のレイアウトルールの考え方が取り入れられています。 各エリアは基本的にページ単位でユニークになるため id セレクタを使用することも可能です。 ただし、id セレクタは詳細度が高くなるため、使用する場合はセレクタ名にプレフィックスをつけるか、属性セレクタを用いることが推奨されています。

Object レイヤー

Object レイヤーは OOCSS の考え方が取り入れられており、サイト内で再利用されるデザインのパターンをすべて "Object" と定義します。

Component レイヤー

Component レイヤーは再利用できるもっとも小さなパターンとしてモジュールを定義します。 命名規則として、プレフィックスには "c-" を付けます。

Component レイヤーでは、最低限のスタイル定義を行い、幅・高さ、色などモジュール固有の特色を持つことは非推奨とされています。 具体的に定義されるモジュールとしては、ボタン、ナビゲーション、ページネーションなどがあります。

Component の設計品質が高く、汎用的であれば他のプロジェクトにおいても転用が可能です。 なぜなら Component レイヤーで定義されるパターンは抽象的なデザイン記号であり、プロジェクト毎の特色を持たないからです。 例えば、ハンバーガーメニューのボタンアイコンは、どのサイトでも同じようなデザインであり、他のプロジェクトでも Component として再利用できます。

Project レイヤー

Project レイヤーは Component と同じように再利用できるパターンを定義しますが、定義する範囲が Component も狭く、より具体的です。 命名規則として、プレフィックスには "p-" を付けます。

Project レイヤーでは疑似要素のスタイルや、colorwidth などのプロパティを設定します。 具体的には、記事一覧、ユーザプロフィール、画像ギャラリーなど、コンテンツを構成する要素が該当します。

.p-boxA {
    width: 100px;
    height: 100px;
    color: #ff0000;
}
.p-boxB {
    width: 200px;
    height: 200px;
    color: #00ff00;
}
Project レイヤーに定義するスタイル

しかし、プロジェクトによっては Project レイヤーに含めるモジュールの定義が難しい場合があります。 なぜなら、"再利用するパターン" の回数に明確な境界線がないため、サイト内で 2 ~ 3 回しか使い回されていないモジュールの場合、Project レイヤーに含めても良いのか判断が難しくなります。 多くのプロジェクトでは Project レイヤー に含める再利用回数を決めて、それより下ならばページ個別のスタイルに振り分ける方法を取っています。

Utility レイヤー

Utility レイヤーは Component や Project では解決できない調整用のレイヤーであり、比較的小さなスタイルを定義します。 Utility レイヤーは Component や Project の肥大化を防いだり、それらのレイヤーに定義することが適切ではないスタイルを定義する役割を担います。 例えば、clearfixmargin などの小さなスタイルが該当します。 命名規則として、プレフィックスには "u-" を付けます。

禁止・非推奨・許容・推奨

FLOCSS で禁止とされているルールは以下の通りです。

  • 状態を表す is- から始まるセレクタにスタイルを定義すること
  • 同一レイヤー内におけるモジュール間のカスケーディング
  • モジュール間の Extend
  • レイヤーの順序を前後させる

FLOCSS で非推奨とされているルールは以下の通りです。

  • Layout レイヤー以外の要素で id セレクタを使う

FLOCSS で許容とされているルールは以下の通りです。

  • 異なるレイヤー間でのモジュールのカスケーディング
  • モジュール内のカスケーディング
  • モジュール内の Extend
  • is- プレフィックスをもつクラスを使う
  • 隣接セレクタ、疑似クラスの使用
/* CSS側 */
.c-tabs {
  display: table;
  table-layout: fixed;
}
.c-tab {
  display: table-cell;
}
.c-tab > a { // Use Descendant selector
  display: block;
}

<!-- HTML側 -->
<ul class="c-tabs">
  <li class="c-tab">
    <a>Tab A</a>
  </li>
  <li class="c-tab">
    <a>Tab B</a>
  </li>
  <li class="c-tab">
    <a>Tab C</a>
  </li>
</ul>
モジュール内のカスケーディングを許容する例

FLOCSS で推奨とされているルールは以下の通りです。

  • MindBEMding に則った命名を行う
  • Object にプレフィックスをつける
  • モジュール単位でファイルを分割する
├── foundation
│   ├── _base.scss
│   └── _reset.scss
├── layout
│   ├── _footer.scss
│   ├── _header.scss
│   ├── _main.scss
└── object
    ├── component
    │   ├── _button.scss
    │   ├── _dialog.scss
    ├── project
    │   ├── _articles.scss
    │   ├── _gallery.scss
    │   └── _profile.scss
    └── utility
        ├── _clearfix.scss
        ├── _margin.scss
        ├── _position.scss
        ├── _size.scss
Sass を採用したモジュール単位でのファイル分割例

まとめ

CSS はその単純さゆえに大規模なプロジェクトほど統一されたルールがなければ、簡単に破綻してしまいます。 CSS の設計手法や命名規則に絶対の正解はないため、プロジェクトやサイトの特性に合わせて考案されている手法の中から最適なものを選択することになります。

また、途中でルールを変更することは難しいため、最初に決めた設計方針によって CSS 設計の正否がかかっていると言っても過言ではありません。 もしも、あなたが設計方針の決定権を持っており、CSS に関する深い知識を持っていない場合は、CSS の有識者に相談することをおすすめします。

ページトップに戻る