はじめに
Webアプリケーションのバックエンド開発が多かったのですが、最近フロントエンドの仕事も増えてきたので、CSS設計を学ぼうと以下の本を読みました。
CSS設計完全ガイド ~詳細解説+実践的モジュール集
色々と勉強になったので、大切だと思ったことを残しておこうと思います。
なお、本書には実際のコードがたくさん載っていたのですが、そこまでしっかり読み込めていません。
文章をざっと読み、気になるコード例を読んだというレベルです。
その他のコード例は必要に応じて参考にしたいと思っており、必要になったときに思い出せるようにという意味も含めてブログに残しておきます。
CSS設計
CSS基礎
CSSはカスケードという同じ要素の同じプロパティに異なる値が設定されていた時に、どれを適用するかという規則がある。
具体的には以下の優先度順で適用するスタイルが決定される。
- 重要度
- 詳細度
- IDセレクター(
#example
など)
- クラスセレクター(
.myClass
など)
属性セレクター([type="radio"]
や [lang|="fr"]
など)
擬似クラス(:hover
, :nth-of-type(3n)
, :required
など)
- 要素型セレクター(
p
, h1
, td
など)
- コードの順序
詳細度について少し補足。
IDセレクターが最強。クラスセレクターを大量につけていてもIDセレクターに対する設定には勝てない。
同じ「クラスセレクター・属性セレクター・擬似クラス」の場合は、たくさんついている方が優先度が高い。
詳しくはこちらを参照。
CSS設計のゴール
- 予測できる
- スタイリングが期待通りに振る舞うか
- スタイリングの影響範囲が予測できるか
- そのために、新しいスタイリングを適用しても自分の意図しない箇所に影響を与えないことが大切
- 再利用できる
- 既存のパーツを別の箇所で利用できるか
- そのために、スタイリングがきちんと抽象化され、適切に分離されていることが必要
- 保守できる
- 新しいモジュールや機能を追加・更新・配置換えしたとき、既存のCSSを修正する必要がないか
- 拡張できる
- CSSに携わる人が1人でも複数人でも問題なく管理できる状態か
CSS設計の8つのポイント
CSS設計のゴールを実現するための8つのポイント。
- 特性に応じてCSSを分離する
- HTMLとスタイリングが疎結合である
- 影響範囲がみだりに広すぎない
- 特定のコンテキストにみだりに依存していない
- 詳細度がみだりに高くない
- クラス名から影響範囲が想像できる
- クラス名から見た目・機能・役割が想像できる
- 拡張しやすい
特性に応じてCSSを分離する
設計手法によって異なるが、例えば以下のように分離する。
- ベースグループ
- サイト全体に適用されるべきスタイルを分類
- bodyタグなどに直接適用する
- レイアウトグループ
- レイアウトに関するスタイルを分類
- 位置調整(paddingやmarginなど)を担当するコードは分離してレイアウトグループとして管理する
- モジュールグループ
- サイト内全体で使いまわしたいものを分類
- そのモジュール自体のあしらい、及び子要素のスタイリングのみに関心をもつべき
- 「自分がどこに、どのようなサイズで配置されるか?」については、レイアウトグループやそのモジュールが使われるコンテキストに任せる
- 具体的には、以下のようなレイアウトに関する指定は極力つけない
- position(static, relativeを除く)
- z-index
- top / right / bottom / left
- float
- width
- margin
- これらをつけないことで、ブロックレベルの要素の特性に従い、親要素の横幅100%になり、またモジュールを複数縦に並べた場合、上下の余白がなく詰まった状態になる
- そうすることで、再利用性が高くなる
HTMLとスタイリングが疎結合である
要素型セレクター(p
, h1
, td
など)や属性セレクター([type="radio"]
や [lang|="fr"]
など)を使うとHTMLとスタイリングが密結合になる。
密結合になると、HTMLを変えた時にCSSも変える必要が出てくる。
そのため、要素型セレクターや属性セレクターは極力使用せずに、クラスセレクターを使用する。
影響範囲がみだりに広すぎない
ベースグループのように意図して広くしている場合は問題ない。
無駄に広すぎる場合は、意図せずスタイリングが適用されて大変(負債となる)。
そのため、以下を意識してスタイリングする。
- スコープを絞る(影響範囲を狭くする)
- 影響範囲の広いCSS(ベースグループなど)に含めるスタイリングは、なるべく最小限に留める
特定のコンテキストにみだりに依存していない
例えば、#main .bl_module
というセレクターは#main
の中でないと適用されない。
つまり、#main
というコンテキストに依存している。
モジュールは「サイト内全体で使い回したい」ので、「#main
の中でないと適用されない」のは望ましくない。
詳細度がみだりに高くない
詳細度が高いと以下のような問題がある。
- セレクターの見通しが悪くなりがち
- 他の要素(親要素など)に対する依存が多くなりがち
- 上書きが難しい
- ゆえにメンテナンスの工数が増える
そのために、IDセレクターは使用せずに、クラスセレクターを使用する。
IDはJavascriptなどでのみ利用するのが基本。
クラス名から影響範囲が想像できる
CSSは全てがグローバルスコープなので、CSSを保守していく上では特に、クラス名から影響範囲が想像できることが大切。
もう少し具体的に記載すると「影響範囲が狭いか広いか、クラス名からきちんとわかるようにする」ことが大切。
これには様々な設計手法があるが、一つ具体的な例を出す。
例えば、モジュールの子要素があったする。
子要素はモジュールの中でしか機能しないが、この子要素のクラス名が.title
とかだとモジュールの外でも使えそうに思えてしまう。
この解決策として「モジュールの子要素には、モジュールのルート要素のクラス名を継承させる」という方法がある。
.title
が.bl_card
の子要素であれば、.bl_card_title
とつける。
こうすることで、モジュールの子要素をモジュールの外で持ち出して使おうとはなりづらくなる。
クラス名から見た目・機能・役割が想像できる
プログラミングにも共通しているが名前から役割を想像できることは大切。
例えば、以下のようなクラス名だとどのような役割を果たすのか想像できない。
以下のように役割がわかるようにつけるべき。
- page-title
- section-title
- sub-title
ただし、具体的すぎると汎用的でなくなり、使い回しを前提としたモジュールに対する名前としては不適切。
モジュールに対する最適な命名は以下のようなものになるだろう。
- コンテキストではなく、見た目・機能・役割をベースとする
- media・accordion・sliderなど、一般的な呼称を使用する
拡張しやすい
拡張しやすさには以下の2つの観点がある
- 拡張しやすいクラス設計を行う(マルチクラス設計を採用する)
- 拡張用として作成したクラスは、機能・役割に応じて適切な粒度・影響範囲を保つ
拡張しやすいクラス設計を行う
拡張しやすいクラス設計とは、ずばりマルチクラス設計のこと。
設計にはシングルクラス設計とマルチクラス設計がある。
シングルクラス設計とは、HTMLにつけるモジュールのクラスを常に一つに絞る方法であり、
マルチクラス設計とは、モジュールに関するクラスを見た目や機能・役割に応じて適宜分解し、HTMLに複数つけることを許容する方法。
例えば、赤いボタンと青いボタンがあり、それぞれ色以外は同じスタイリングとする。
シングルクラス設計の場合は、赤いボタン用のクラスと青いボタン用のクラスを作成し、それぞれのHTMLにつける。
マルチクラス設計の場合は、色以外のスタイリングをするクラス、赤色にするクラス、青色にするクラスの3つを作成し、
赤いボタンの場合は、色以外のスタイリングをするクラスと赤色にするクラスの二つをつける。
また、例えば追加で、ボックスシャドウがついたボタンが必要になっても、マルチクラス設計の場合は、ボックスシャドウ用のクラスを作成するだけで良いので、拡張性が高い。
このように、シングルクラス設計はHTMLがシンプルになるメリットがあるが、
CSSが肥大化したり、モジュール拡張に対する柔軟性が低くなったりするので、マルチクラス設計の方が良い。
拡張用として作成したクラスは、機能・役割に応じて適切な粒度・影響範囲を保つ
拡張用として作成したクラスとは、ずばりモディファイアのことである。
モディファイアを機能・役割に応じて適切な粒度・影響範囲を保つようにすることが大切。
そのために気をつけるべきポイントは以下。
「モディファイアをつける箇所(作成するモディファイアの数)は、変更を加える要素数と一致させるのではなく、提供する機能(または役割)一つに一つのモディファイアを作成するようにする」
例えば、左側に画像があり、右側にテキストブロックがある一般的なメディアモジュールがあるとき、これを左右反転したモジュールを追加で作成したいとする。
なお、親クラス(.bl_media
)とその子要素に画像クラス(.bl_media_imageWrapper
)とテキストブロッククラス(.bl_media_body
)があるとする。
このとき考慮することは以下の3つ。
- 画像とテキストブロックの入れ替え(
.bl_media
の修正)
- 画像とテキストブロックの間のマージンの調整(元のモジュールは画像にmargin-rightを設定していたため、何も調整しないと画像とテキストブロックがくっついてしまう)(
.bl_media_imageWrapper
の修正)
- テキストを右揃えに変更(元のモジュールは左揃え)(
.bl_media_body
の修正)
このとき、それぞれのクラスのモディファイアを作成するのではなく、親クラスのモディファイア(.bl_media__rev
)を作成し、その子要素で画像とテキストブロックのスタイリングの上書きをする。(子要素のスタイリングの上書きは親クラスに.bl_media__rev
があるときにのみ機能する)
なお、親クラスだけにモディファイアがつけるべきという話ではなく、子要素だけにモディファイアをつけても良い。
大切なことは、「ひとつのモディファイアはひとつの機能(または役割、変更)と紐づいている」ということ。
こうすることで、モディファイアをHTMLから取った場合の影響範囲が予想しやすくなる。
モディファイアの基本は、「モディファイアをつけ外すだけで、モジュールに変更を加えることができる」こと。
モジュールの粒度
チーム内でモジュールの粒度の認識にばらつきがあると、ひとつのクラスの大きさ、つまりコードの実装方法にもばらつきが出てしまう。
そのため、モジュールの粒度の認識はチーム内ですり合わせておくべき。
最適解はプロジェクトによって異なるが、最低限、以下の2つの単位があるということを認識しておくべき。
- 最小モジュール: ボタンやラベル、タイトルなどのシンプルな要素)
- 複合モジュール: いくつかの子要素をもつ、ひとかたまりの要素
具体例
書籍を読んで「なるほど!」と思ったことを箇条書きで記載します。
めちゃめちゃメモ書きなので、前後の文脈がなくてわかりづらいものもあるかと思います。
- クラス名は「それが何なのか」を表すようにする
- エラーメッセージのクラスは
.red-text
ではなく、.error-text
にする。その方が保守性が高い。
- クラス名はなるべくセマンティック(意味のある)ものにすべき。
- レイアウトに関することはレイアウト用のクラスに任せる
- モジュールはレイアウトに関して気にする必要はない(責務の分離)
- ボタンモジュールには
display: inline-block
をつける
- 段落内でテキストと一緒に使用される場合、段落で指定している文字揃え(
text-align
)の向きにボタンの位置も従うことができる
- ボタンの幅を固定したい場合はwidthプロパティを指定すると、長いテキストが入る際はボタン内で改行する。
逆にボタン内で改行したくない場合は、min-widthプロパティを使用する。
スクリーンサイズが狭くなった際にボタンが見切れないように、max-width: 100%
を指定する。
- 文字サイズはpxではなく、remを使う
- ブラウザのフォントサイズ変更機能を使っていても、remであればそれに合わせてフォントサイズも変更される
- なるべく各ユーザの設定を受け入れるようにしておくべき
- ただし、ボタンサイズなどはフォントサイズ変更機能で変化して欲しくないのでpx指定でサイズを固定する
- このように「その単位を使う意味」をきちんと考えて使用する
- 例えば、フォントサイズの変化に応じて自動的に余白を増減させたい場合はemを使う
- margin-topやmargin-bottomなどの順番は、marginのショートハンドの順番に合わせておくと良い。
つまり、margin-top, marigin-right, margin-bottom, margin-leftの順。
- 行間(行の余白)が指定したサイズ以上に空いてしまうことがある。
これはline-heightにまつわる仕様に起因する。
- 参考(https://html5experts.jp/takazudo/13339/)
- 行の上下に「line-height – font-size」から算出されたサイズが割り当てられる。
この割り当てられたサイズのことを「ハーフリーディング」と呼ぶ。
- 上下の余白を管理している箇所は、あらかじめ最後の子要素の余白を積める設定をしておくと、要素が増減した場合も自動的に余白の制御を行える
- 画像を天地中央でトリミングするテクニック。
以下の3つの指定で画像の上下位置を調整し、width: 100%;で横幅いっぱいに表示されるように指定できる。
今後想定しないサイズの画像がサムネイルとして使用されても、ある程度まで自動で天地中央でトリミングできる。
- position: absolute;
- top: 50%;
- transfrom: translatY(-50%);
- Can I use(https://caniuse.com/)というサイトで、各プロパティの各ブラウザでの対応状況を確認できる
- コンテンツはきちんとHTMLに記述すべき
- before擬似要素のcontentプロパティなどでテキストを表示することができるが、情報がCSSにあるのはマシンリーダブルではなく好ましくない
- CSSで少し複雑なことをするものは多くの場合ジェネレーターが存在する
- 例えば、三角形はCSS triangle generator(http://apps.eky.hk/css-triangle-generator/)などがある
- 複合モジュールそれ自体にレイアウトに関わるスタイリングを行うことは好ましくない。
レイアウトの変更が必要な場合は、ラッパーモジュールを用意するなどして、必ず一つ上の親要素から制御を行うべき
- フレックスボックスなどで「カードモジュールを3カラムずつ複数行に並べる」みたいなスタイリングは良くある。
このとき、「行間は30px空けるが、最後の行だけは空けない」というスタイリングすることも良くあると思う。
この場合は、カードモジュールにmargin-bottom: 30px;
つけて、ラッパーモジュールにmargin-bottom: -30px;
つけると最後の行の余白が相殺されて、意図通りのスタイリングになる。
- カードモジュールで意識すること
- カードモジュールはそれ自体を独立したひとつのモジュールとして扱う
- カラムを形成する場合は、専用のラッパーモジュールを作成し、そのラッパーモジュールからカードモジュールのレイアウト指定を行う
- 例えば3カラムのときは、3カラム用のモディファイルを作成し、そのモディファイアからカードモジュールの横幅やガターの値を指定する
- メディアクエリ適用時のスタイルがカラム数に関わらず同じ場合は、セレクターにカラム数のモディファイアを含めない
white-space: no-wrap;
でテキストが折り返されないようにすることができる
- スワイプの強さに応じてスクロールの速度や量が変わること機能を慣性スクロールと呼ぶ。
-webkit-overflow-scrolling: touch;
で慣性スクロールを有効にできる。
ただ、このプロパティは標準仕様でないので注意。
pointer-events: none;
で、ホバーやフォーカス、クリックなどのポインター関連のイベントの対象外にできる
参考(https://developer.mozilla.org/ja/docs/Web/CSS/pointer-events)
- ユーザの具体的な行動を換気するためのインターフェースをCTA(Call To Function)と呼ぶ
- CSScomb(しーえすえすこーむ)という便利ツールがある
- CSSのプロパティの並び順の設定
- 色指定のアルファベットは小文字か大文字かどちらにするかの設定
- クォーテーションはダブルクォテーションかシングルクォーテーションの設定
- CSS Statsに解析したいページのURLを入力すると、CSSルールの数やセレクター数、使用されている色、フォントサイズなどさまざまな情報を一覧して見ることができる
- こちら(https://cssstats.com/)
The following two tabs change content below.