CSSの理想と現実
CSSはゆるいルールの上で書ける便利な言語です。しかし、どのようにでも書けてしまうだけに一定の良い習慣を実践していないと際限ない肥大化、詳細度地獄、ネスト地獄といったアンチパターンに陥ってしまうことがあります。
カオスになる前にコードの臭い(wikipedia)を嗅ぎ取って理解しやすく修正しやすい書き方を心がけていきたいですね。
スタイルの打ち消しを避ける
まず心がけたいのが、一旦定義したスタイルをなるべく打ち消さないようにすること。
早すぎる最適化に注意
smell.css
h2 { font-size: 2em; margin-bottom: 0.5em; padding-bottom: 0.5em; border-bottom: 1px solid #ccc; } .no-border { /* 打ち消し */ padding-bottom: 0; border-bottom: 0; }
aroma.css
h2 { font-size: 2em; margin-bottom: 0.5em; } .headline { padding-bottom: 0.5em; border-bottom: 1px solid #ccc; }
見出しに下線を引くという早すぎる最適化がなされた上のコードを下のように直すことによってコード量も10行から8行に減り見通しも良くなりました。
ドナルド・クヌース氏曰く「早すぎる最適化は諸悪の根源」だそうです(カッコいい)。CSSにおいても早すぎる最適化はスタイル打ち消しの元であり避けるべきアンチパターンと言えます。OOCSSにおいては構造と見た目(スキン)の分離は重要な概念ですが、どんなCSS設計を採用するにせよどこからが使い回しの効く部分で、どこからがそうでないのかを見極めておきたいですね。
既存コードから早すぎる最適化された箇所を見つけるには
border-bottom: none;
padding: 0;
float: none;
margin-left: 0;
といったキーワードでcss/sassファイル内を検索してみましょう。
否定擬似クラスを活用しよう
smell.css
li { border-bottom: 1px solid red; } li:last-child { /* 打ち消し */ border-bottom: 0; }
aroma.css
li:not(:last-child) { border-bottom: 1px solid red; }
ナビゲーションやテーブルを作るときに頻出する最後のli要素や最後のtd要素だけに打ち消しスタイルを当てたいときも、否定擬似クラス:not()
を使えばもっとスマートに記述できます。
↑の例ではコードが6行から3行に短縮され、スタイル打ち消しもなくなりました。
li + li {...}
のように隣接セレクタを使うパターンも考えられますね。
親クラスを変えて繰り返し定義しない
アンチパターン
smell.css
/*_button.scss*/ .button { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; } /*_modal.scss*/ .modal .button{ padding: 5px 10px; margin-bottom: 10px; }
smell.html
すいませんこれ私もやっちゃうときあります…がよくないですね。
この書き方の何が問題かと言うとSassの@import機能を使う場合など、コンポーネントをパーシャル化するメリットがなくなってしまうから。.button
クラスがあっちでもこっちでも(_button.scss, _modal.scss…)再定義されてしまい、今表示されている.button
はどこで書いた定義が当たっているのか、またそれを修正した場合の影響範囲はどこまでかが理解しづらくなってしまいます。
良い例
aroma.css
/*_button.scss*/ .button { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; border: 1px solid transparent; border-radius: 4px; } /*_modal.scss*/ .modal_button{ padding: 5px 10px; margin-bottom: 10px; }
aroma.html
既存クラスを再利用しつつ特定のページやコンポーネントだけに適用するスタイルを当てたいときには新しいクラスを作る方が良い。例はマルチクラスですが設計によってはシングルクラスにしてもいい。設計手法を問わず使える方法であり、セマンティクス警察もニッコリです。
アイコンを作る編
次にアイコンを作るときを例にCSSコーディングを考えてみます。
属性セレクタを使う
aroma.css
i[class^='icon-'] { position: relative; width: 19px; height: 18px; } i[class^='icon-']:before { position: absolute; top: 15px; left: 10px; width: 19px; height: 18px; content: ''; background-repeat: no-repeat; background-size: 19px 18px; } .icon-user:before { background-image: url(/images/icon_user.png); } .icon-tel:before { background-image: url(/images/icon_tel.png); }
属性セレクタを使った書き方です。この例だと20行とアイコン編で最も短く書けました。ちなみに↑はclass属性の値がicon-
から始まるi
要素という意味になります。
HTML側をマルチクラスにする必要がないので使い勝手も良い。
CSSセレクタのマッチングコストは若干上がりますが、ファイルサイズ減少、コードの見通し向上といったメリットとのトレードオフとして考えると許容範囲だと思います。
OOCSS
aroma-oocss.css
icon { position: relative; width: 19px; height: 18px; } icon:before { position: absolute; top: 15px; left: 10px; width: 19px; height: 18px; content: ''; background-repeat: no-repeat; background-size: 19px 18px; } .icon-user:before { background-image: url(/images/icon_user.png); } .icon-tel:before { background-image: url(/images/icon_tel.png); }
23行。パフォーマンス面も考慮するとこれが一番良いかも。デメリットは必ずマルチクラスになることくらいですね。
冗長だけど悪くない a.k.a 悪くないけど冗長
not-too-bad.css
.icon-user { position: relative; width: 19px; height: 18px; } .icon-user:before { position: absolute; top: 15px; left: 10px; width: 19px; height: 18px; content: ''; background-repeat: no-repeat; background-size: 19px 18px; background-image: url(/images/icon_user.png); } .icon-tel { position: relative; width: 19px; height: 18px; } .icon-tel:before { position: absolute; top: 15px; left: 10px; width: 19px; height: 18px; content: ''; background-repeat: no-repeat; background-size: 19px 18px; background-image: url(/images/icon_tel.png); }
↑これだと32行。重複コードが多用されDRY原則的にはよくないですが、コードの見通しは良く、gzip圧縮を前提にすればパフォーマンスも高くなる記法です。ただやっぱり個人的には冗長さを感じてしまうかも…
段々辛くなるパターン
not-too-good.css
.icon-user, .icon-tel { position: relative; width: 19px; height: 18px; } .icon-user:before, .icon-tel:before { position: absolute; top: 15px; left: 10px; width: 19px; height: 18px; content: ''; background-repeat: no-repeat; background-size: 19px 18px; } .icon-user:before { background-image: url(/images/icon_user.png); } .icon-tel:before { background-image: url(/images/icon_tel.png); }
クラス共通部分をカンマで繋ぐ書き方、23行。これは一見良さそうなのですが、アホほど長いセレクタチェーンができる、一つのクラスだけを直そうとすると結局は別な場所に上書きをする必要が生じるといった@extend問題でだんだん辛くなる可能性がある点に注意。
その他
子孫セレクタを避ける、IDセレクタを避ける、!importantを避けるなどのCSSにおけるよく知られたグッドプラクティスももちろん大事ですね。
所感
OOCSS, SMACSS, BEMの三大設計思想を抑えておけば他に学ぶものはない…そんなふうに考えていた時期が俺にもありました。しかし近年Atomic Design, ECSS, Scoped CSSなど独自のCSS設計も数多提唱されています。
また「予測しやすく」「保守しやすく」「再利用しやすく」「拡張しやすい」CSSを作るという観点とパフォーマンス観点が時には相反関係になったりもします。そんな時に立ち返るのはやはり基本的な考え方になるはず。
というわけでこれからもどんな場面にも応用が効くコーディングでCSSを消臭していきたいと思います。
参考
- Code smells in CSS
- Code Smells in CSS Revisited
- [CSS]否定疑似クラス「:not」の便利な使い方と使う時の注意点
- CSS3のセレクタ全42種 まとめておさらい使い方リファレンス
- @extendを使うべき時、@mixinを使うべき時