聞いたら一生の宝、プログラミングの基礎の基礎

第9回CSS設計の基礎を見直す

みなさんこんにちは。teratail開発チーム デザイナーの平井優です。

Webサービスの制作において、閲覧者にとって情報を視覚的に伝わりやすくするためにはページのスタイル調整が欠かせないと思います。そんな重要な役目を担っているのがCSS(Cascading Style Sheets)というスタイルシート言語です。CSSを利用すればHTMLで記述された文書を読みやすくするだけでなく、3D、アニメーションなどのリッチな表現も可能になります。

そんな便利なCSSなのですが、長期的に運用していると予想外の不具合や、ほんの少しの変更をするだけで見た目が大きく崩れてしまうといった問題がしばしば起こってしまいます。

[参考]teratailでの質問件数(2015/12時点)
CSS:761件
CSS3:327件

今回は、そういった不具合によって⁠後で困らない⁠ために、「CSSの設計」の基本的な考え方をアンチパターンなど具体的な例を交えながら紹介していきます。

CSSを設計することの重要性

CSSはとてもシンプルかつ学習コストも低く、気軽に書くことができますよね。teratailでは、フロントエンドエンジニアやデザイナーでなくとも、ブラウザのデバッグツールを用いてCSSを編集しながら一時的なプロトタイプとして表示確認する光景なんかも珍しくありません。

しかし、そんな簡単に書くことができ自由度が高いものであるからこそ、大規模サイトの運用フェーズや関係者が多いプロジェクトでは以下のような問題が起こることがあります。

  • 予測していなかった箇所のスタイルが崩れる
  • 思った通りにスタイルが反映されず、新しい記述で上書きをしてCSSが煩雑になる
  • 期待する場所以外のスタイルが変わってしまい、過去のCSSを修正せざるを得ない
  • ファイルの肥大化により、どこを編集したらいいのか分かりづらい
  • 既存のスタイルを利用すれば更新は必要ないのに、重複したスタイルを定義してしまう
  • ...etc

いかがでしょうか、みなさまも目にしたことや経験したことがございますでしょうか。簡単な記述の追加だったとしても、上記のような理由により予想以上の時間がかかってしまうことがあります。そんな状況になってしまうことを未然に防ぐため、CSSの設計が重要です。

CSS設計を行う上で意識すること

後で困ることのない、効率的かつ正確な開発をしていくために意識しておくポイントを簡単に追っていきましょう。

予測しやすい
機能追加・見た目の変更・修正を行う際に「どのスタイルが変更されるのか」または「どのスタイルは変更されないのか」が理解できる状態。
保守しやすい
既存のCSSのリファクタリングをできるだけ行わずに、新しい見た目や機能を追加できる状態。
再利用をしやすい
汎用的なスタイルは、定義したCSSの形を変えずに、どのような場合でも複数箇所で使い回すことができる状態。
拡張しやすい
機能の追加・修正などの拡張をすることを前提として、他のどの開発者がアサインされたとしても低学習コストでソースコードを理解し、サイトの拡張ができる状態。

設計が破綻しやすいCSS

次に、前述のようなメンテナブルなCSSに対する以下のアンチパターンを用いて、設計の実例を紹介していきます。

  • HTMLの構成に依存している
  • スタイルの打ち消しを使用している
  • 詳細度によるスタイルの上書きを多用している

HTMLの構成に依存している

以下のようなナビゲーションリストを実装し、リンクの部分aタグに対してスタイルを定義しました。とりあえず、以下の記述で期待するスタイルは実現できます。

<ul class="navigation">
    <li><p><a>link1</a></p></li>
    <li><p><a>link2</a></p></li>
</ul>
.navigation li p a {...}

数日後、以下のようにpタグを削除するマークアップに変更する機会がありました。この変更により、aタグに対してスタイルが適用されません。そのため、CSSの変更を行いました。

<ul class="navigation">
    <li><a>link1</a></li>
    <li><a>link2</a></li>
</ul>
.navigation li a {...}

上記のようなことを繰り返していては、ナビゲーションリストの構造の変更のたびにCSSを変更しなければなりません。

以下のような実装であれば、このケースに対応できます。

<ul class="navigation">
    <li>
        <p><a class="navigation-link">link1</a></p>
        <a class="navigation-link">link2</a>
    </li>
</ul>
.navigation-link {...}

極端な例でしたが、ユニークなセレクタに対してスタイルを定義することでHTMLの構造と見た目が分離され、たとえば以下のように全く違う構造になったとしても対応ができます。

<div class="navigation">
    <a class="navigation-link">link1</a>
    <a class="navigation-link">link2</a>
</div>

スタイルの打ち消しを使用している

以下のような見出しの実装があったとします。上下の余白や下線を適用させたくない場合を考えてsimpleというクラスで、margin: 0;border: none;という打ち消しの指定をしています。

<h2 class="heading simple">シンプルな見出し</h2>
...
<h2 class="heading">デコレーション見出し</h2>
...
.heading {
    font-size: 40px;
    font-weight: bold;
    margin-top: 10px;
    margin-bottom: 10px;
    border-bottom: 2px solid black;
}
.simple {
    margin: 0;
    border: none;
}

こういったケースでは、⁠打ち消す」より「共通のプロパティに対して追加する」という考え方をすると、ソースコードの記述量も減り見通しが良くなります。

<h2 class="heading">シンプルな見出し</h2>
...
<h2 class="heading decoration">デコレーション見出し</h2>
...
.heading {
    font-size: 40px;
    font-weight: bold;
}
.decoration {
    margin-top: 10px;
    margin-bottom: 10px;
    border-bottom: 2px solid black;
}

CSSの記述が2行分削減されました。この範囲だけ見ると小さいものではありますが、⁠スタイルの打ち消し」がCSSの肥大化の大きな原因として挙げられる場合が多いです。

詳細度によるスタイルの上書きを多用している

詳細度とはCSSプロパティを適用させる上での優先度のことで、詳細度が高いセレクタのプロパティが優先的に見た目に影響します。各セレクタの詳細度は、高い順に並べると以下のようになります。

  • !important
  • style属性に記述されているCSS
  • IDセレクタ
  • classセレクタ・属性セレクタ・擬似クラス
  • 要素セレクタ・擬似要素
  • ユニバーサルセレクタ

例として、#contentに含まれるpタグに対して、文字色をグレーにするスタイル指定を行います。

p {color: black;}
#content p {color: gray;}
<p>黒にしたいテキスト</p> <!-- 表示:黒 -->
<div id="content">
    <p>グレーにしたいテキスト</p> <!-- 表示:グレー -->
</div>

pタグには元々黒色にするような指定を行っていたので、#contentというIDセレクタを用いることで詳細度を上げて上書き対応しました。

そして数日経った後、新しい意図としてredという文字色を赤くするための汎用クラスを定義し、contentIDの中のpタグに適用させました。

.red {color: red;}
<p>黒にしたいテキスト</p> <!-- 表示:黒 -->
<p class="red">赤にしたいテキスト</p> <!-- 表示:赤 -->
<div id="content">
    <p class="red">赤にしたいテキスト</p> <!-- 表示:グレー -->
</div>

ところが、赤いテキストを期待する箇所が#contentに含まれるpタグのみグレーで表示されていました。

プロジェクトが長期的に運用され、CSSファイルが増える・長くなるとこういった不具合が散見するようになってきます。この現象を未然に防ぐためには可能な限り詳細度による上書きを行わず後述する「コンポーネント化」を行うと効果的です。

teratailで、詳細度に対して言及されているQ&Aがございましたので紹介いたします。

参考:cssを指定しやすいhtmlのidの付け方
https://teratail.com/questions/19872

キーワードは「コンポーネント化」

CSSは、今紹介した通り記述が全てグローバルなスコープとなります。よって、CSSが巨大になっていくほどに影響範囲の特定・管理が難しくなり、さまざまな不具合を引き起こす原因になってしまいます。

このリスクを回避するために効果的な手段として、CSSがもつ役割を特定の機能や見た目に限定する形で、細かく 「分離」 するというものがあります。これを「コンポーネント化」と呼びます。

たとえば、Webサイトにおいて使用頻度が高いであろう「ボタン」を例に取ると、⁠問い合わせ」「購入する」ボタンがあったとします。コンポーネントの概念を取り入れた場合、⁠ボタンであることを表す」⁠問い合わせボタンであることを表す」⁠購入するボタンであることを表す」ためのCSSに分離し、依存関係の無いようにします。

図1 ⁠ボタン」のコンポーネント化のイメージ
図1 「ボタン」のコンポーネント化のイメージ

図1のように「共通化できるCSS」「固有の属性を持つCSS」が生まれるので、上手く使えば変更に強いメンテナブルなCSSが書けそうですよね。このような分離の考え方は、オブジェクト指向CSS(以下OOCSS)と呼ばれています。

OOCSS

OOCSSでは、⁠構造(structure)と見た目(skin)の分離」⁠コンテナー(container)と内容(content)の分離」という2つの基本概念があります。

構造(structure)と見た目(skin)の分離

「構造」はインターフェースが持つ不変的な形を定義し、⁠見た目」はそのまま、その都度変わる固有の見た目を定義します。

先ほどの「ボタン」の例のように、繰り返して定義されるような決まった構造を持つものはその都度見た目と一緒に記述するのではなく分離させてCSSを書きます。

<a class="button button-contact">お問い合わせ</a>
/* ボタンの共通プロパティ */
.button {
    border-radius: 3px;
    box-shadow: 0 1px 1px 0 rgba(0,0,0,.14),0 2px 0px -3px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
    text-align: center;
}

/* お問い合わせボタン固有のプロパティ */
.button-contact {
    background-color: blue;
}

コンテナー(container)と内容(content)の分離

HTML要素や構造、コンポーネントが存在する「場所」に依存しないCSSを実現するための概念です。CSSに対応するHTMLがどのタグだったとしても、同じ見た目を提供できるようにしましょう。

h1 {...}

上のように要素セレクタを限定して書いてしまうことはせず、次のようにすることでHTMLタグの変更がある度に見た目が変わってしまうというリスクが軽減されます。

先述のアンチパターン「HTMLの構成に依存するCSS」は、こういったルールを設けることで回避することができます。

.title {...}

また、コンポーネントは「どの場所にあるか」という依存性のあるプロパティは分離すべきです。たとえばメインカラムとサイドカラムの中のテキストリンクを指定すべく下記のようにして書くことは良い設計とは言えません。

#mainColumn .textLink {
    color: blue;
    text-decoration: underline;
}
#sideColumn .textLink {
    color: blue;
    text-decoration: underline;
}

次のようにすることで、場所にとらわれること無く再利用をすることができ、不要な記述をせずに済みます。

.textLink {
    color: blue;
    text-decoration: underline;
}

当然のように思われるかもしれませんが、CSSのファイルが増えたり、テキストリンクのスタイルケースが増えていったりすると、詳細度を上げるべく場所の指定を行ってしまうことが実際にあります。こういった不具合になり得る小さなものまで普段からチェックしておきたいものですね。

最後に

いかがでしたでしょうか。今回はCSSを書くにあたって⁠後で困らない⁠ために、基本的な設計の考え方を具体的な例を交えながら紹介いたしました。

ただ、ここで本当に重要なのはこれらの概念を覚えることではありません。実際のプロジェクトチームの中で議論し、長期的にCSSとどうやって向き合っていくかということに対してメンバー間の共通の意識を持ちつつ、実践することだと思っています。今回紹介した概念をひとつのアイデアとして「自分たちに合ったCSS設計」をガイドラインなどのドキュメントに起こし、運用できる体制をとってみてはいかがでしょうか。

おすすめ記事

記事・ニュース一覧