SMACSS 読んだ
Scalable and Modular Architecture for CSS (日本語) を読んだのでそのメモです。
CSSルールのカテゴライズ
カテゴライズを行い、それに準じた命名をセレクタに付ける。
- ベース
- レイアウト
- モジュール
- 状態(ステート)
- テーマ
レイアウトには1つ以上のモジュールを保持する必要がある。
モジュールは最利用可能なパーツとする。
命名規則
レイアウト、状態(ステート)、モジュールにはプリフィックスを使用する。
レイアウトのスタイルにはlayout-
を付ける。または、ドキュメントなどでコーディング規約をまとめてあるなら省略してl-
と付けても良い。
状態(ステート)にはis-
を付ける。
モジュールは作成される数が多いので、モジュールごとにプリフィックスを付ける。
/* Example */ .comment { } .comment-user { }
ベースルール
- クラスやIDが使用されるケースはなく、ページ全体で要素がどう見えるかを定義する。
- ベースルール内で !important が使用されるケースはない。
- body の背景色、特に色の指定が必要ないときでも白に指定しておいたほうがいい。ユーザーによってはデフォルトが白でないケースがある。
CSSリセット
- CSSリセット( reset.css など)は一度ブラウザが作っているベースを再設定することになるので、ブラウザが読み込むコードが増える。そのため、使用を避ける。
- 意図しないスタイルが当てられる可能性もあるので、使用の際は十分に中身を把握することが必要。
- 時間とやる気が許すならプロジェクト単位のデフォルトスタイルを作るのがベスト。
下記の記事なども参考に、CSSリセットについてはよく一度考える必要がある。
なぜリセットではなく Normalize.css を使うのか
レイアウトルール
レイアウトのスタイルを定義する際は前提条件(リストが左にフロート、幅・高さが固定されているなど)ができるだけ無いようにする。
- 主要なもの(header, footer, asideなど、html5でアウトライン構造を持たせるために定義されているもの)はidで付け、再利用可能と思えるものはclassで
.layout-flipped
などのように指定する。 - idセレクタはそもそも固有のものであるはずなので、
div#header
などと付ける必要は無い。不必要な命名は避ける。 - idセレクタは固有のものであるため、プレフィックスを付ける必要性は薄い。
/* 高次レベルのレイアウトスタイルが他のレイアウトスタイルに影響を与える例 */ /* .l-flipped クラスが body 等の高次レベルの要素に適応された場合に #article と #sidebar のコンテントが入れ替わる。) */ #article { float: left; } #sidebar { float: right; } .l-flipped #article { float: right; } .l-flipped #sidebar { float: left; }
/* 流動レイアウトから固定レイアウトに切り替える例 */ #article { width: 80%; float: left; } #sidebar { width: 20%; float: right; } .l-flipped #article { width: 600px; } .l-flipped #sidebar { width: 200px; }
モジュールルール
それぞれのモジュールはスタンドアロンのコンポーネントとして存在できるようにデザインされるべきである。
- モジュールの子セレクタや子孫セレクタを要素セレクタに適用するのは問題ないが、それはセマンティックを含む場合にのみ要素セレクタを使うこと。span や div にセマンティック性は無く、見出しにはあり、要素に対してクラスを設定した場合は多分にある。
- 要素セレクタを使う場合にはクラスセレクタ内の1つ下の階層に留めるべき。
- モジュールのサブクラスを作る際には、ベースモジュールとサブモジュールのクラスを分けて作成しなければいけないケースが有る。
/* 詳細度の問題解決 */ .pod { width: 100%; } .pod input[type="text"] { width: 50%; } .pod-constrained input[type="text"] { width: 100%; } .pod-callout { width: 200px; } .pod-callout input[type="text"] { width: 180px; }
<div class="pod pod-constrained"> ... </div> <div class="pod pod-collout"> ... </div>
状態(ステート)ルール
状態とは他のすべてのスタイルを拡張して上書きするものである。
サブモジュールと状態スタイルの大きな違いとして、下記がある。
- 状態スタイルはレイアウトやモジュールに割り当てることができ
- 状態スタイルはJavascriptに依存するという意味を持つ
important!の使用は他では避け、状態ルールの中でのみ使用する。
テーマルール
まず、このルールは使用する機会は少ない。
- ブログのテーマや、ユーザーカスタマイズ出来るサービス作成時に使用する。
- 大規模サイトで他言語対応時には、中国語や韓国語などの言語では標準のフォントサイズでは小さいことがあるので、そういう時にテーマルールを使用することもある。
- 規模の大きなテーマを作成する場合には
theme-
プリフィックスを付ける。 - テーマルールの命名はセマンティックなものになりにくいが(スタイルに依存するものが多い)、タイポグラフィの命名においてはHTML内で見出しを定義するのと同様にセマンティック性を重視して付ける必要がある。
その他
- レイアウトスタイルは位置や配置のためにあるものであって、フォントや色を指定するものではない。
- サイト内でフォントサイズは3-6個。6個以上のフォントサイズはユーザーが気づかないのにメンテナンス性を下げるだけ。
状態の変更について
状態の変更が起こる3つのパターン - クラス名( jacascript によって変更される) - 擬似クラス - メディアクエリ
状態の変更が伴うスタイルは、「何をデフォルト状態にするのか」というのを疑問を持つことが必要である。
親要素(モジュールなど)にステートスタイルを付与するのではなく、兄弟要素にスタイルを振ることで変更したい箇所が明確化される。
サブモジュールの命名規則
/* サブモジュールの命名規則 */ .btn { color: #333; } .btn-pressed { color: #000; } .btn-disabled { opacity: .5; pointer-evenets: none; }
状態の命名規則
/* 状態の命名規則 */ .btn { color: #333; } .is-pressed { color: #000; } .is-disabled { opacity: .5; pointer-evenets: none; }
属性セレクタの例
参考: Change State - Attribute Selector
/* 属性セレクタの例(CSS) */ .btn[data-state=default] { color: #333; } .btn[data-state=pressed] { color: #000; } .btn[data-state=disabled] { opacity: .5; pointer-evenets: none; }
/* 属性セレクタの例(HTML) */ <button class="btn" data-state="disabled">Disabled</button>
CSSアニメーションとJavaScriptの使い分け
理解しておくべき違いは、 CSS が見た目上の状態を定義するのに使われているということだ。JavaScript を使って要素の状態を切り替えることはできるが、JavaScript は状態を説明するのに使うべきではない。つまり JavaScript を使ってインラインのスタイルに追加すべきではないとも言える。
適応性の深度
関連性が高い悪い例
/* bad case */ #sideba div { border: 1px solid #333; } #sideba div h3 { margin-top: 5px; } #sideba div ul { margin-bottom: 5px; }
この構成はHTMLとの関連性が強く、CSS を見ただけで HTML の構造が大体把握できてしまう。ここでは、サイドバーに最低1つの見出しとリストを持ったセクションがあると想定している。 この CSS には2つの懸念がある。
- 定義されたHTML構造への依存性が高い
- セレクタが適応されるHTMLの深度が深すぎる
深度を最小限に留める
深度を最小限に留める方法として、単純なモジュール化を行う。
.pod { border: 1px solid #333; } .pod > h3 { margin-top: 5px; } .pod > ul { margin-bottom: 5px; }
<div class="pod"> <h3>heading</h3> <ul> <li>item</li> </ul> </div>
上記ではul
が使われてるが、ol
やdiv
が使われることも想定できる。その時は、ルールを重複させて指定することもできるし、.pod
の body にクラスを振ることで解決することもできる。
.pod-body { margin-bottom: 5px; }
<div class="pod"> <h3>heading</h3> <ul class="pod-body"> <li>item</li> </ul> </div>
セレクタパフォーマンス
まず初めに、パフォーマンスについてはベストケースとワーストケースの差分は50msだった。言い換えると、セレクタのパフォーマンスについては考慮するべきではあるが、多くの時間を費やすべきではない。
パフォーマンスの測定は Google Page Speed 等で行う。
CSSはどのように評価されるか
要素に対するスタイルは要素が生成されるタイミングで評価される。
ブラウザのことを一から知る(読み込み方法など)なら、ブラウザの仕組み: 最新ウェブブラウザの内部構造 | HTML5 ROCKSなども一読する必要がある。
ブラウザは CSS のセレクタを右から左に向かって評価を行う。
例として、body div#content p { color: #003366; }
のようなルールの場合、
- ページ内でレンダリングされながらすべての要素に対して、その要素が段落要素であるかを確認する。
- 仮に段落要素だとしたら、DOM の最上部に向かってコンテンツという ID が付与された div を探す。
- その要素が見つかったら、また DOM の最丈夫に向かって探索を初め、body が見つかるまで続く。
どのルールが優先されるか
パフォーマンスが良くないとされる4つの例
- 子孫セレクタのルール。(例: #content h3)
- 子セレクタまたは隣接セレクタのルール。(例: #content > h3)
- 必要以上のセレクタがあるルール。(例: div#content > h3)
- リンクではない要素に対する:hover
を利用したルール。(例: div#content:hover)
最も大事な点は、スタイルを確定するのに1つの要素以上を評価することは非効率であるということだ。
詳細は Minimize payload size を参考にする。
SMACSSの著者が自分に定めてる3つのルール
シンプルな3つのガイドライン
HTML5とSMACSS
SMACSS のゴール
- HTMLとコンテントのセマンティックな価値を向上すること
- 特定の HTML 構造への依存を低減すること
メインナビゲーションの実装例 - Bad Case と Good Case
Bad Case
<!-- bad case --> <nav class="nav-primary"> <h1>メインナビゲーション</h1> <ul> <li>アバウト <ul> <li>チーム</li> <li>所在地</li> </ul> </li> </ul> </nav>
/* bad case */ nav.nav-primary li { display: inline-block; } nav.nav-secondary li, nav.nav-primary li li { display: block; }
Good Case
<!-- good case --> <nav class="l-inline"> <h1>メインナビゲーション</h1> <ul> <li>アバウト <ul class="l-stacked"> <li>チーム</li> <li>所在地</li> </ul> </li> </ul> </nav>
/* good case */ .l-inline li { display: inline-block; } .l-stacked li { display: block; }
プロトタイプ
プロトタイプはコンポーネント全体、あるいは一部を可視化し、デザイン言語をコード化し、ブロックを構築することを補助する。
プロトタイプ作成に mustache は便利かもしれない。 mustache
mailclimp はすでに自分たちのプロトタイプを持っている。参考にする。
MailChimp Design Patterns
プリプロセッサ
深い入れ子ルール
Sass などのプリプロセッサで下記のようになることは珍しいことではない。
/* bad case */ #sidebar { width: 300px; .box { border-radius: 10px; background-color: #eee; h2 { font-size: 14px; font-weight: bold; } ul { margin: 0; padding: 0; a { display: block; } } } }
/* 上記sass変換後 */ #sidebar { width: 300px; } #sidebar .box { border-radius: 10px; } background-color: #eee; } #sidebar .box h2 { font-size: 14px; font-weight: bold; } #sidebar .box ul { margin: 0; padding: 0; } #sidebar .box ul a { display: block; }
SMACSS による深い入れ子ルール
SMACSS ではもともと深い入れ子を使わない。レイアウトとモジュールの分割に寄ってこれらの問題を回避する。
/* good case */ #sidebar { width: 300px; } .box { border-radius: 10px; background-color: #eee; } .box-header { font-size: 14px; font-weight: bold; } .box-body { margin: 0; padding: 0; a { display: block; } }
長いセレクタの連鎖はブラウザが要素に対してスタイルが適応されるか否かを確定するのに余計な負荷をかける。
不必要な拡張
Sassで下記のような例はモジュール化ができておらず、散財することになるので避けるようにする。
/* bad case */ .box { border-radius: 5px; box-shadow: 0 0 3px 0 #000000; background-color: #003366; } .btn { @extend .box; background-color: #0066cc; }
SMACSS の拡張方法
extend を別モジュールで利用しない。 SMACSS の拡張方法は、HTML で複数クラスを割当て、HTML レベルのみで解決できる。
<!-- SMACSSモジュールクラスをボタンに割り当てる --> <a class="btn btn-default" href="">ボタン</a>
Mixinsの使いすぎ
モジュールのスタイルが似ている時は、モジュール単位で Mixin を作るのではなく、モジュールに割り当てられるスタイルルールで1つ Mixin を作り、それと隣接してモジュールクラスを付ける。
入れ子ルールと状態を元にしたメディアクエリ
入れ子ルールを使ったメディアクエリの例
.nav > li { width: 100%; @media screen and (min-width: 320px) { width: 100px; float: left; } #media screen and (min-width: 1200px) { width: 250px; } }
ファイル編成
プロジェクト内でどのようにファイルを分離するかのガイドライン
- ベースルールはすべて1つのファイルに記述する。
- レイアウトの種類に応じて、すべて1ファイルに記述するか、主なレイアウト毎にファイルを作成する。
- モジュールはそれぞれのファイルに記述する。
- プロジェクトの規模によってはサブモジュールも個別のファイルに記述する。
- グローバルで利用する状態は専用のファイルに記述する。
- レイアウトやモジュールに関連するメディアクエリを含む状態は関連するモジュールファイルに記述する。
ディレクトリ構成サンプル
+-layout/ | +-grid.scss | +-alternate.scss +-module/ | +-callout.scss | +-bookmarks.scss | +-btn.scss | +-btn-compose.scss +-base.scss/ +-states.scss/ +-site-settings.scss/ +-mixins.scss/
最後にマスターファイルを用意する。
プリプロセッサはこれらを1つのファイルとして変換してくれる。
@import "site-settings", "mixins", "base", "states", "layout/grid", "module/btn", "module/bookmarks", "module/callout";
公開前にCSSの圧縮を行う。
// Sass を使った CSS の圧縮 sass -t compressed master.scss master.css
アイコンモジュール
background-position
指定でスプライトを指定した時の弊害
- 包括するアイテムの中でしか使用できず、特定のHTML構造に依存する。
- 他のモジュールで利用する際にスプライトを再度定義する必要がある。
- フォントサイズを大きくするだけでもスプライトの他のパーツが見えてしまうかもしれない。
- 横並びのスプライトしか利用できず、xのポジションは0で固定されているため、右から左に文字が配置されるインターフェースを実装するのが難しくなる。
スプライト画像でアイコンを表示させる例
<!-- good case --> <li><i class="ico ico-16 ico-inbox"></i> 受信箱</li>
/* good case */ .ico { display: inline-block; background: url(/img/sprite.png) no-reapat; line-height: 0; vertical-align: bottom; } /* ico-(size), ex.16, 24, 32, 48.. */ .ico-16 { height: 16px; width: 16px; } /* ico-(sprite image position) */ .ico-inbox { background-position: 20px 20px; } .ico-drafts { background-position: 20px 40px; }
画像圧縮化を行う際に便利なツール
- smushit.com/ysmush.it/
- imageoptim.pornel.net/
単一行と複数行
単一行のコードフォーマットは、プロパティの見通しよりも、セレクタの見通しを重要視したインデント。
プロパティをセレクタ内に多く書く現在では、セレクタの見通しとしてよくない。
参考: SINGLE LINE CSS
複数行のコードフォーマットは、プロパティの前に4スペース空け、プロパティを種類でグループ化し、色の宣言は短縮形を使う。
現在では、こちらを使用したほうがコードの見通しは良い。
プロパティをグループ化
1セレクタ内での指定の際、各プロパティをグループ化する時の例(列挙通りの順で書く)
- ボックス(表示とポジション)
- ボーダー
- バックグラウンド
- テキスト
- その他
色の宣言
色の指定は、一番短いhex(#xxxxxx)を使う。理由は、最大6つ、最小3つで宣言できるから。
hex で指定できない場合はもちろん rgba , hsla を使用する。