自分でゼロからCSSを書く時のおはなし。
CSSの保守性と見通しを高めて、プロジェクトに対するモチベーションを維持するためにやってることをまとめてみました。
主にCSSを保守するのがめんどくさいという事に対する愚痴が書いてあります。完全な持論ですので「そんなの絶対おかしいよ!」と思う事、あると思います。つっこみお待ちしております。
簡単なまとめ
- クラスで切るんじゃなくてメタ言語のMixin(Function)で切る
- よく使う値は変数で管理する
- Bootstrapを逃げ道にするのはやめよう
長いけどこれだけです。以下は時間がある人だけ読んでください。
前提
- Stylusというメタ言語を使用しています。Rubyistは適宜
SCSSに読み替えるなどしてください、文法は殆ど同じです。 - StylusはLESSと違ってサーバサイドでのみコンパイル可能なメタ言語なので、どんな非効率なコードだろうと気にする必要ないと思ってます。
- ブラウザのCSSレンダリング速度はプロトタイピング中は気にしなくていいかなって思ってます。
- クラスベースのCSSをdisってるわけじゃないです。
わたしがまずいと思ってること
最近のイケてるサービスやBootstrapなどのCSSフレームワークでは、主にクラスベースでスタイルを切り分けてます。あれはHTML4.*とCSS2.*における完成系です、最初からあそこに辿り着こうと思ったらプロジェクトが崩壊します。
クラスベースのスタイライズを行うと、HTMLとCSSが密結合になります。スタイルの適応や修正にHTMLをいじる必要が出てきます。HTMLを過度に編集するとJSが不審な挙動を起こし始めることもよくあります。そして、JSのためにHTMLを編集するとそれに伴ってCSSも編集する必要に迫られることすらあります。
つまり、途中でスタイルを見直すと「CSSを編集⇄HTMLを編集⇆JSを編集」というコンボがキマり、結局全てのファイルを弄るハメになる場面が多々あります。スタイルを変更するのにロジックもいじらないといけないなんて変ですね。クソ食らえです。
結局、後に残るのは「全てを捨ててスマートなプロダクトをゼロから再実装したい」という欲求です。
個人や少人数など、足が軽い状態で実装していると「ゼロから再実装したくなる欲求」を抑制することは非常に困難です。加えて、そのような欲求に苛まれた際にもう引き返せないラインに到達していると、実装意欲の急激な低下を招き、結果としてプロジェクト放棄の原因となります。オナ禁っぽいですね。
これらを防ぐためにも、プロトタイピングにおいて「スタイル調整に伴うViewの変更」は不要とされるべきです。HTMLとCSSは疎結合であって欲しいというのが私の願いで、スタイルのことはスタイルの中で完結するべきなんです。
またプロダクション環境であっても、Viewの変更はユーザからキャッシュを奪うことになるので非効率なんじゃないかなって思います。
長い繰り返しコードはFunctionにして外部化する
微妙に値を変えながら繰り返して使用されるテンプレートはFunctionにします。
よくない例
例えば「マージン5pxの合計60x60pxのオブジェクトにviewからbackground-imageを指定して、要素の短辺を満たす最大サイズにfitさせたい」という場合、こんな風に書くと思います。
.picture(style='background-image:url(#{pict})')
.picutre
margin 5px
width 50px
height 50px
background-color transparent
background-size contain
background-repeat no-repeat
background-position 50% 50%
クラスベース信者はすぐにこうしたがります。
.picture.background-contain(style='background-image:url(#{pict})')
.picture
margin 5px
width 50px
height 50px
.background-contain
background-color transparent
background-size contain
background-repeat no-repeat
background-position 50% 50%
後から他のスタイルでbackground-positonやmarginを弄る必要が出るとこうします。
.picture.background-contain(style='background-image:url(#{pict})')
.image.background-contain.background-position-topleft(style='background-image:url(#{img})')
.picture
margin 5px
width 50px
height 50px
.image
margin 10px
width 20px
height 20px
.background-contain
background-color transparent
background-size contain
background-repeat no-repeat
background-position 50% 50%
.background-position-topleft
background-position 0 0 !important
何れもViewを弄る必要性が出てきます。もしmarginやwidthやheightもクラス化したい気分になっていたらそれはもう終わりの始まりです。
これではダメです。
よい例
.picture(style='background-image:url(#{pict})')
.image(style='background-image:url(#{img})')
background_fit(bgsize, leftposition = 50%, rightposition = 50%)
background-color transparent
background-size bgsize
background-repeat no-repeat
background-position leftposition rightposition
.picture
margin 5px
width 60px
height 60px
background_fit contain
.image
margin 10px
width 20px
height 20px
background_fit contain 0 0
background_fitの定義部が長くて全体の見通しを悪化させるので外部化します。
.picture(style='background-image:url(#{pict})')
.image(style='background-image:url(#{img})')
@import 'background_fit'
.picture
margin 5px
width 60px
height 60px
background_fit contain
.image
margin 10px
width 20px
height 20px
background_fit contain 0 0
もしmarginやwidthやheightも自動化したかったらこんな定義もあり得ます。
background_fit_box(margin, boxsize, bgsize, leftposition = 50%, rightposition = 50%)
margin margin
width w = (boxsize - margin*2)
height w
background-color transparent
background-size bgsize
background-repeat no-repeat
background-position leftposition rightposition
.picture
background_fit_box 5px 60px contain
スマートです。
共通の色など、よく使う値は変数で管理する
stylusなどのメタ言語に乗っかってもせいぜい{}:;の省略とインデント程度しか使わず、MixinやVariableやConditionを活用しないし、むしろ活用しない方が実装速度が早いという人はいると思います。
でも、なんでそれが実装されてるのか?という点から考えれば自明の便利さがあります。

例えば、これをカラーベースにしてサイトを組もうと思い立ちます。
html, body
color #3D3936
background #ECE8DF
#header
color #ECE8DF
background #3D3936
#article
background #FCFDFD
&:hover
background #F1F1F2
後から見たら「どこに」「どの色が」「どういう方針で」使われたのか全くわからないし、全部でどれだけの色数があるのかもわからない。
Typoがあってもきっと気づかないし、スキーマ変更に全文リプレイスを使用していると、その煩雑さから途中で「これでいいや(・ω・`)」という気持ちになってしまいます。
なので、こうします。
whitecolor = #FCFDFD
whitecolor_highlight = #F1F1F2
basecolor = #ECE8DF
basecolor_highlight = #CEC9C0
maincolor = #3D3936
maincolor_highlight = #1E1A17
accentcolor = #24B9EC
accentcolor_highlight = #089ACE
html, body
color maincolor
background basecolor
#header
color basecolor
background maincolor
#article
background whitecolor
&:hover
background whitecolor_highlight
headerだけ色が反転している、hoverした時にのみ*_highlightを使う、等の情報がわかりやすくなります。
「コードから色の意味がわかるようにする」というのは有効な手段だと思います。レンダリングされたUIが意図した通りに動作・発色するかを効率的にテストする手法が無い(と思ってるけどあるのかな)ので、実際にhoverして間違った色を使ってないかチェックする手間は最後だけで済みます。
スキーマ変更時も変数を書き換えればよいだけなので、プロトタイピングも捗りますね。
もっと厳密な制約を課したい場合は、関数化するのもありです。
color_scheme(schema)
if schema is 'regular'
color maincolor
background basecolor
if schema is 'inverse'
color basecolor
background maincolor
if schema is 'article'
color maincolor
background whitecolor
&:hover // これをここに書くべきかどうかは要判断
background whitecolor_highlight
html, body
color_schema 'regular'
#header
color_schema 'inverse'
#article
color_schema 'article'
また、*_highlightカラーが元のカラーから一定の法則で生成できるのならば、下記のように色を定義することも可能です。これはstylusの機能なので他にあるかは知りません、LESSではありました。
whitecolor = #FCFDFD
whitecolor_highlight = darken(whitecolor, 10)
basecolor = #ECE8DF
basecolor_highlight = darken(basecolor, 10)
maincolor = #3D3936
maincolor_highlight = darken(maincolor, 10)
accentcolor = #24B9EC
accentcolor_highlight = darken(accentcolor, 10)
共通する色や値の管理はCSSコーダーに取って結構な課題だと思います。何らかの方法を使ってスマートに解決しましょう。
最後に
みんながBootstrapに逃げたがるのって、うまいCSSの書き方メソッドを持ってなくてViewを浸食してぐちゃぐちゃにしちゃった経験に起因してるのかなって思ってます。保守性の高いCSSって語られる機会があまりないし、ぐぐってもSCSS万歳みたいな記事しか出てこなかった。
デザインが画一化するとインターネットがつまらなくなるので、こういう感じでCSSの保守性を高める自分なりのメソッドを見つけて、独自のCSSを楽しく書く人が増えればいいなって思います。
あと、CSSのレンダリング速度がどうしても気になる人は、「プロダクトが完成してから」リファクタリングを行いましょう。
あと、メタ言語にstylusを採用したら光属性ライブラリnibはチェックしましょう。global-reset()とか、border-radius・linear-gradient()といった先進的実装(笑)をベンダープレフィックスで自動展開とか、clearfixの機能なんかを拡張してくれます。
以上です。インターネットの平和を願って。やみのま。
stylusでCSS吹き出しを簡単に実現するfukidashi.stylつくりました
after要素を使ったCSSの吹き出しツールチップ、毎回実装方法をぐぐってる気がするのでFunctionにしました。
コピペ元の要素順番がぐちゃぐちゃで直したり、{}:;を除去する作業だったり、なんか長ったらしくなって気持ち悪くなったり、そういった現象を華麗に回避できます。
使い方もgistに収録したけど一応解説する。
.item1
fukidashi white 5px of 60px on left
これで「白くて」「底辺10px、高さ5pxで」「親オブジェクトの30px位置を中心とする」「左向きの三角形を親オブジェクトの左側に」くっつける指定です。

こういうイメージ
60pxのところにautoを入れると、50%位置が中心となります。
of、onはどれが何の値かわからなくなりそうなので空の変数として突っ込んでるだけなので、元の関数をいじってもらえれば修正できます。
jsのファンなのでjs使うのやめます
CoffeeScriptについて説明したスライドです、増井研の合宿で使いました。
某Y氏に「情基礎っぽい」と言われた。
猿真似デザインメソッドで表紙だけYanone使ったりしてます。
自動でvalidateしてくれるObjectが作れるの作った
腐る程ありそうだけどいちおう作った。
[Getter/Setter]を使ってオブジェクトのプロパティをバリデートしましょうという、それだけです。
主な特徴としては、
orderには'date'か'name'を入れたいけどif order is 'date' or order is 'name'するのだるいvalidate: (val) -> return /hoge/.test valとかバリデータ書くのだるい
そんな(自分の)要望に応えて、type:['date', 'name']とかtype: /hoge/として定義できるようになってます。
詳しい例なんかは上記リポジトリ参照で。
node.jsの2回目のrequireでcacheを使わない
node.jsのrequireは、一度でもrequireした全てのオブジェクトをキャッシュする。なので、2回目以降の同一ファイルに対するrequireではキャッシュされたオブジェクトが流用される。
通常はこの仕様で困らないし、むしろスコープの制約からrequireが頻発するnode.jsでは非常に便利な機能である。
困るケース
例えば、長期間Runさせる必要のあるアプリケーション内部でsetIntervalなどによりcron相当の機能を実現する場合。
「長期間Runさせる=アプリケーションを長期間落としたくない」と解釈するならば、setIntervalで実行されるファイルはメンテナンスの手間を考えて都度最新のものがrequireされることが望ましいかもしれない。
だが、setInterval内部でrequireしても使用されるのはキャッシュされたオブジェクトであり、よっぽどメモリが足らなくならない限りキャッシュされたオブジェクトが未来永劫使い回しされる。
どうするか
hoge.jsというファイルがあったとする。
require(path.resolve('hoge'));
こうすると、require.cache[path.resolve('hoge.js')]にキャッシュオブジェクトが生成される。
なので、deleteする。
require(path.resolve('hoge'));
delete(require.cache[path.resolve('hoge.js')]);
これでキャッシュオブジェクトを削除できる、次回以降のrequireで再びファイルを取得して評価してくれる。
追記 5/11
clusterを使っているならforkすればいい