理想と現実のCSS設計


株式会社ピー・アール・オー アドベントカレンダーの20日目の予定でした(力尽きて投稿遅れました)。

CSS設計は語られることが多く、「素敵!ベストプラクティス!」は数あれど、実行するのは難しい。

この記事は「ベストプラクティス!」ではなく、無間地獄を生成しないためのTipsです。

CSS設計の理想

基本の「き」ですが、すごく大事なことなのでおさらい。
CSS設計は「できる4原則」。

  1. 予測できる
  2. 再利用できる
  3. 保守できる
  4. 拡張できる

ここからスタートするわけですが、様々な制約が出現して、断念せざるを得ないこともあります。

しかし。

仕事において「ものごとを実現させる」というのは、理想と現実のど真ん中に解決法を落とし込むことだとも思うのです。

転機は「スゴい」と「ヒドい」で出来ている

20数年スタコラ仕事をしていれば、転機となる瞬間は幾度も訪れるわけで、現在のCSS設計に行き着いたのも、楽しい理由あってこそ。
その中でも、愉快すぎて死んでしまいそうになった事案を一部ご紹介。

1. 開発画面は驚きの白さ。CSSリセットは計画的に

「とりあえず、開発機を動かしてみてよ」と言うことで、テストURLを叩いてみました

…ログインできませんやん。

「会員管理システム」なので、大量に入力フォームが出てくるのですが、すべての画面が驚きの白さ。ヘッダとフッタ、テキストしかわからん。
「入力してください」って、どこに入力フォームがあるのだ!

ふとtabを連打してみると、不思議なところにカーソルが出る。

原因はユニバーサル リセット(全称セレクタ リセット)の内容。

ユニバーサルリセット
* {
        padding: 0;
        margin: 0;
        border: 0;
        outline: none;

}

な、なにやらかしてくれてるの!
marginpaddingまではいいけど、その後のborderとかoutlineってどういうことさ!

テーブルのラインどころか、キーボード操作のフォーカスまで消してる!アクセシビリティ的に悪質オブ悪質っ!
この内容は完全に滅びの呪文

ブラウザは各々デフォルトCSSを持っているので、デザインを当てる時に邪魔になることはあります。
そのため、必要な部分だけリセットするのは一般的。
しかし、面倒を嫌って、ユニバーサルリセットになんでも突っ込む人もいるらしい。
とっぱじめから手を抜くのはよくない (・A・)イクナイ!!

参考:よく使われるリセットCSS

部分的にリセットして幸せになろうよ…。

2. フレームワークはどこにいったの?カスタマイズは辛いよ

BootstrapをはじめとするCSSフレームワークを利用するのも最近の流れ。
フルスクラッチでHTMLやCSSを書くより、開発工数を圧縮しやすいのも利点の一つです。

だがしかし。

開発当初は「Bootstrapが用意しているデザインでOK」としていたところ、「なんとなく、ちょっと付け足したい」から始まり、要望が爆増
結果、「なんとなく、多分Bootstrapの残骸」と思えるほどの見た目と、フルスクラッチ以上の労力がかかってしまったというのはタマにある。

CSSフレームワークは非常に便利。利用しない手はない。
けれど、フレームワーク外のことをやろうとすると、かなり手が掛かる
ベースとなっているフレームワークのCSSが邪魔をして、思ったようなレイアウトにならないことも多く、フルスクラッチで構築した方が早いこともあります

参考:2020年版:おすすめの人気CSSフレームワークと特徴の総まとめ

予め用意されているレイアウトでは吸収できない時は、外枠だけ利用するなど、安易に採用しないことも大事だと思います。

3. CSSに破壊の神がやってくる。情報キャッチアップは大事

諸般の事情で、開発中に担当会社が一部変更になることがあります。
その場合はCSSの設計思想も引き継ぐので、通常使わないCSS設計も軽く知っておく必要があります。

ある案件で3社目の担当となり、それまで制作していたデーターの引き継いだ時のこと。
デザイン通りの実装もできておらず、CSS設計も破綻しています。
担当した1社目はOOCSS(Object Oriented CSS)を用いていましたが、2社目が制作を引き継いだ後、設計が完全に破綻してしまったようです。
IDやCLASSの命名も行き当たりばったりで、まったく規則性がありません。これでは内容を予測することもできない

まあ、理由は簡単な話です。
2社目はOOCSSによる設計思想が理解できていなかった

最近は情報源も多く、無料で公開されているものも多いです。
ちょっと手を伸ばすと大量に情報を入手できます。
CSSの設計手法にしても

…などありまして、中規模以上のWebサービスは、これらを使用していることが多いでしょう。
完全に把握していなくても、いずれも予測がつきやすいので、設計を引き継ぐのはそれほど難しくはありません

しかし、情報のキャッチアップを怠っていると無理の無理なわけで、CSS設計を破壊した後に待ち受けるのは「死ぬほど辛い地獄のような更新」のみです。
つまり、自分の首を絞める

ひとりで作り、ひとりで維持し、死ぬまで付き合えるなら、「ボクがかんがえた、さいきょうのせっけい」でも良いのですが、人間の命には限りがあるので、そういうわけにもいかない。

CSS設計の前提は「できる4原則」であること
新しく編み出す前に、基本の「き」は知っておきたいところ。
型があるから型破り、型が無ければ単なる形無し」とは、有名な歌舞伎役者の言葉ですが、CSS設計にも同じことが言えると思います。

4. 1ページづつデザインを入れ替えよう!気遣いで地獄爆誕

サービスインする前から、デザインのリニューアルが決まってい時のこと。
追加機能の予定もあり、ディレクター氏はこう言った。

「大変だと思うので、1ページづつデザインを入れ替えましょう」

…気遣いです。マジい気遣いでした。
しかし、コーディングサイド的には地獄爆誕です。

そもそも、それ以外も大惨事レベル。

  1. サービスイン直前の段階でCSS設計が破綻。もう二度と見たくない。
  2. プログラムの都合上、いくつかのIDやCLASSは変更できない。
  3. 新デザインは、旧デザインやコーディングに配慮したものではない。
  4. 新デザインの中にも旧デザインを引き継ぐ部分がある。

色々な条件が入り組んでいるため、なかなか難しい状態ですが、「もう見たくない」が全てを物語ってます。
新旧デザインページを破綻させることなく、どうやって入れ替えを完結させるか…。

えぇ…どうるすのぉ…。コレ、私が始末つけるんですかぁ?

地獄を見越してクモの糸を用意する

最初にあった通り、CSS設計は「予測できる」「再利用できる」「保守できる」「拡張できる」ことが大事なのですが、複数いる実装者の実務レベルや、専属のHTML/CSSコーダーがプロジェクトに最後までアサインするか等で考え方が変わってきます。

  • 知識・実務レベルはバラバラ
  • HTML/CSSコーダーは最後までアサインしない・運用も参加しない
  • Viewがテンプレート化された後は、事故防止のためにも極力HTMLの修正を行わない(探すの面倒だし)

この状況下で編み出したので、若干「悪魔の手法」な気もします。
が、「できる限り、誰も地獄を見ない」という最低条件のもと、次のような考えで設計をしていきます。

つまり、カンダタに垂らされた「蜘蛛の糸」をいくつも用意するような感じです。

サービスインしたらCSS設計は壊れていくと考える

完璧に設計しようとも、運用フェーズに入ればCSS設計は壊れていくものだと思ってます。
担当者が変更になったり、状況的に設計を維持できなくなるということは大いにあり得ます。

前提として「壊れるものだ」という認識でいると、精神衛生上はいくらかマシになります。
だって、人間だもの。

ただし、壊れるに任せるのではなく、「できる限りゆるやかに壊れるようにする」努力は必要なので、設計段階で色々と足掻いてみます。

立ち上げ初期はゆるめのルール設計を心がける

設計を開始直後は、ゆるめにルール設計を考えるようにしてます。
CSSは指定方法によって点数がつけられ、それらを計算することによって、適用される優先順位がわかります。

参考1:ご存知、ないのですか?CSSの優先順位
参考2:CSSセレクタの優先度を手軽に計算して比較できるツール「Specificity Calculator

優先度が高い順

  1. スタイル属性(style="…"でHTMLに直書き)
  2. IDセレクタ(p id="sample")
  3. CLASSセレクタ(p class="sample")
  4. 属性セレクタ([href*="…"])
  5. 擬似クラス(a:hover, etc…)
  6. 要素型セレクタ(h1,p, etc…)
  7. 擬似要素(h:firest-child, etc…)
  8. 全称セレクタ(*)

計算方法はややこしいので割愛。

ルールをゆるめに考える」ので、CLASSセレクタを中心に考えていきます。
基本、親要素に依存するような書き方はしません
親要素に依存する書き方をしてしまうと、要素がひとつ外れただけでスタイルが無効になってしまいます。

親要素に依存しないためmainTextが効くのでOK
.wrap {
        padding: 16px;
        margin: 0 auto 42px;
}

.mainText {
        padding: 16px;
        font-size: 1rem;
}
親要素に依存するためmainTextが効かなくなるのでNG
.wrap {
        margin: 0 auto 42px;
        padding: 16px;

}

.wrap .mainText {
        padding: 16px;
        font-size: 1rem;
}

このくらいの内容ならいいのですが、#contact .wrap p.mainText {…}のような書き方が初期の段階からあると、開発終盤にレイアウトの制御が難しくなって詰みます

!importantは使わせない

基本設計が「ゆるめ」である最大の理由。それは!importantを使わせないため。
この呪文を唱えることで、優先度を高めることが可能ですが、乱発すると「本来はどれが優先されたのか?」がわからなくなります。バグの要因にもなります。

こうなると影響するCSSが幾つあるかわからないため、新規追加するCLASSに与えるプロパティの予測がつきません。
予測ができないと、結果を見ながら行き当たりばったりの設定をすることになります。
完全にCSS崩壊です。

!importantは最後の最後、手詰まりになった時にはじめて使うくらいにしておかないと、運用フェーズに入った時に泣きます。

なんちゃって短縮名は使わない

ストレートに書くと名前が長くなるので、短縮名を使うというのはよく見かけます。
業界的によく使われる短縮名なら良いのですが、「名前を短くする」ことが目的になって、よくわからない短縮名が使われていることがあります。

CSSの設計では「予測できること」が重要なので、短縮名がなければそのまま使います。
CSSとかに使う短縮名を考える

また、ケアレスミスがあるとものすごく恥ずかしいので、英辞郎などを使ってスペルを確認しながら命名します。

最後の手段用IDを作る

IDはページ内で繰り返し使うことができないので、もっぱらCLASSでの設計になります。

基本のスタンスは、親要素に依存しない「緩め」な書き方をしますが、開発が大詰めの頃に「1ページだけ文字の大きさを変更したい」とか「1ページだけテキストを中央揃えにしたい」などのオーダーが入ることがあります。

開発終盤ではViewがテンプレート化されているため、該当箇所を探すのも大変だし、1カ所だけのためにclassを新規で追加し、HTMLを修正するというのが何より面倒

そんな時に、予め付けていたIDを利用します。

bodyにIDをつける
<body id="contact">
    <div class="wrap">
      <header>
        <h1>お問い合わせ<h1>
      </header>

      <article>
     <p class="mainText">
        必要項目を入力してください。
       </p></article>

      <footer>
        <p>
          <small>
            &copy; copyright…
          </small>
        </p>
      </footer>
    </div>
</body>

例ではbodyに付けていますが、ページ全体を囲む要素に付ければ良いので、<div id="contact" class="wrap">という付け方でも良いと思います。
問い合わせページだけ文字の大きさを変更したい」というオーダーだった場合、Viewのテンプレートには触れず、CSSだけで制御します。

優先度を高める
.mainText {
        padding: 16px;
        font-size: 1rem;
}

#contact .mainText {
        font-size: 2rem;
}

ID名は管理が面倒にならないようにディレクトリごとに作るようにしています。
「お問い合わせ」であれば、最低3つのページが同じIDになります。

  • 内容入力画面
  • 入力確認画面
  • 送信完了画面

設計初期はゆるいルールで作り、開発終盤ではそれらをIDで締めるイメージです。

ただし、CLASS名から予測できないスタイルの追加であれば、素直に新規でCSSを作った方が良いです。
何事も、ご利用は計画的にという感じです。

テーブルの項目はCLASS名を打ちまくる

最後の手段用IDの流れを組んでいますが、登録する内容が多いサービスの場合、それらの情報を表示するページの項目もかなりの数になります。

テーブルで情報を表示する場合、内容に合わせてテーブルセルは収縮しますが、自動改行したくないセルや、特定セルのWidthを縮めたいというオーダーが出てくる可能性が高いです。

CSSには偶数(奇数)要素だけ、左からn番目だけ…という方法で指定することもできますが、項目の表示順が変わってしまうと大惨事になります。

…ということで、効率的とは言えませんが、素直にCLASSを打ちまくります。

セルを細かく制御するためのCLASS
<table class="memberList" summary="会員一覧">
    <thead>
        <tr>
          <th class="userName">会員名</th>
          <th class="userSex">性別</th>
          <th class="birthday">生年月日</th>
      <th class="joinDay">入会日</th>
          <th class="userAddress">住所</th>
          <th class="userTel">電話番号</th>
          <th class="userEdit">編集</th>
        </tr>
    </thead>
    <tbody>
          <td class="userName">松尾 デラックス 松子</td>
          <td class="userSex">nknown</td>
          <td class="birthday">1970/01/01</td>
      <td class="joinDay">2020/12/12</td>
          <td class="userAddress">東京都…</td>
          <td class="userTel">080-xxxx-xxxx</td>
          <td class="userEdit">編集</td></tbody>
</table>

名前は姓名だけのことがほとんどですが、過去に「ミドルネーム」の登録が必要なサービス開発にアサインしたことがあり、バリデートをどうかけるか?という難問があった記憶があります(どうなったか忘れました)。

名前を自動改行させたくなければuserNameに対してWidthと改行なしを指定すれば良くなります。
あちこちに該当項目があったとしても、同じCLASS名に設定が有効になるので、ひとつづつ追加する作業は減ります

項目が増えるごとにCLASS名を考えるのが大変ですが、この方法とtableのCLASS最後の手段用IDを使えば、もっと細かい制御がCSSのみで行えます。

フレームワーク利用時は追加のCLASSに接頭子を付ける

フレームワークの設計思想と同様の規則が良いとはわかっていますが、カスタマイズ量が増えると難しくなってきます。

ベースのフレームワークが持っているCSSが邪魔をして、思うようにレイアウトが作れないことも多く、様々なプラグインを追加した結果、レイアウトに作用しているCSSがどこかわからなくなります

うまくレイアウトが作れない場合、作用しているCSSを突き止める必要があります。
その切り分けが行えるよう、カスタマイズ用にCSSを追加する場合は、あえて接頭子付きにします。
正直「ぐぬぬぬぬ…」な気分なのですが、地獄めぐりをしたくないので仕方ありません。

この考え方は「1ページづつデザインの入れ替えをする」ところから来てるのですが、この時はv2-などの、バージョン名を接頭子にした記憶があります。

最後は基本に立ち返る

色々書いても、結局のところは基本が大事。
私はカスタマイズしたBEMを使うことが多いですが、規模が小さければOOCSSです。

BEMを使っているのは、SMACSと比べると学習コストが低めかもな、という軽い理由です。
複数人でコーディングを行う場合、メンバーの知識がある程度同等である必要があります。
学習コストが高すぎると脱落するメンバーが発生するため、わかりやすく・壊れにくい設計手法を基準にした時に、BEM(カスタマイズ版)という選択肢が残りました。

CSS設計については、非常に優れた書籍があります。
CSS設計完全ガイド ~詳細解説+実践的モジュール集
私はKindle版で持っているのですが、512ページというボリュームなので、ソフトカバー版はちょっとした鈍器です。
著者が開発した設計手法「PRECSS」の解説もあり、持っておいて損はないと思います。

参考サイト