はじめまして、こんにちは。
ネット総合事業本部プラットフォームDivの斉藤です。

今回は私の所属している部署からは初の1pixelへの寄稿となるそうです。

CSS開発のアプローチ

ほぼ同時期にモダンウェブ開発に欠かすことが出来ないと言われるようになったJavaScriptと比べると、CSSにおける設計、という話題はなかなか出てきません。

単純なウェブサイトを作る際にはあまり気にしてこなかった部分ですが、ウェブアプリを制作している我々の仕事には欠かすことが出来なくなってきています。

ほかのプログラミング言語に比べると圧倒的に非力な言語だからこそ、ほかのプログラミング言語でも重要と考えられているDRY(Do not Repeat Yourself)や、メンテナンス性、そして柔軟性を考慮した設計がより重要だとも言えると思います。

今回はそれらの原則を実行するために必要なコンポーネントについて紹介していきたいと思います。

コンポーネント


コンポーネントイメージの図


オブジェクト指向CSS(OOCSS)Scalable and Modular Architecture for CSS(SMACSS)という単語を聞いたことがあるという方も多いでしょう。
今回紹介するコンポーネントとは、CSSにおけるオブジェクトあるいはモジュールと同じものだと考えてください。
※CSSにおけるオブジェクト、モジュールについては、Scalable and Modular Architecture for CSS(SMACSS)という本もあります。こちらについては現在私の方で和訳をおこなっています。

90年代のウェブサイトと比べると現代のウェブサイトには非常に多くのパターンを見つけ出すことができるようになりました。
コンポーネントとはそれらのパターンと言い換えることが出来ると思います。

ウェブサイトに限らず、人間はパターンを好む生き物です。心理学、神経科学のジャーナリストであるジョナ・レーラー氏は著書『一流のプロは「感情脳」で決断する​』でこのように述べています:

身の回りに見たことがあるパターンを見いだすと、我々の脳はドーパミンを発する。

ドーパミンは快楽と結びつきが強いと考えられているホルモンだということなので、人間はパターンを好むという意見の裏付けにもなっています。
コンポーネントアプローチはユーザにとっての『使いやすさ』を補強するだけではなく、我々開発者にとっても有益に働くことが多いWin-Winのアプローチです。

  1. ヴィジュアルパターンを抽出し、抽象化することでコードの繰り返しを避けることが出来ます。このことがDRYの原則を守ることにつながり、結果的にコードの絶対量を減らすことが可能になります。


  2. 抽象化されたコードは拡張性に優れていることから、柔軟性も高くなり、結果的にモダンウェブ開発では欠かせないイテレーションを素早く回すための基礎として優れていると言うことが出来ます。


  3. 1)と2)であげた特性はメンテナンス性を高めるために必要な要素です。またこの後で紹介するコンポーネントアプローチを行うためのワークフローを活用することでより強固なメンテナンス性を得ることが出来ます。


ビジュアル言語とも言えるCSSに効率性を求めるのは非常に難しいとされてきましたし、そのCSSの基礎である記述性が失われたわけではないため簡単なことではありません。
どんなコンポーネントがウェブサイトにはあり、それらをどのようにコード化していくのかを見ていきましょう。

コンポーネントリストとコード化

コンポーネントの最もわかりやすい例はボタンだと思います。
試しにGoogleで「CSS ボタン」と検索してみてください。驚くほどに多くの情報があるはずです。ボタンには様々なバリエーションがあります:

  • サイズ
  • 状態(:hover:focusなど)

などがバリエーションの糧となる好例でしょう。

小規模のウェブサイトにも状態を含めたボタンのバリエーションは20くらい、最低でも10はあるはずです。CSS3の登場により、それらのバリエーションをコードだけで実現できるようになった代わりに、すべてのバリエーションを個別のコンポーネントとしてコーディングしてしまうと無駄の多いコードを生み出すことになってしまいます。

典型的なボタン(:hover:activeの状態を含む)のCSS例: 
.login {
  padding: 10px 15px;
  background: #4479BA;
  color: #FFF;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  border: solid 1px #20538D;
  text-shadow: 0 –1px 0 rgba(0, 0, 0, 0.4);
  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  -webkit-transition-duration: 0.2s;
  -moz-transition-duration: 0.2s;
  transition-duration: 0.2s;
  -webkit-user-select:none;
  -moz-user-select:none;
  -ms-user-select:none;
  user-select:none;
}

.login:hover {
  background: #356094;
  border: solid 1px #2A4E77;
  text-decoration: none;
}

.login:active {
  -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
  -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
  background: #2E5481;
  border: solid 1px #203E5F;
}

この例では、ログインボタンそのものと:hover:activeの状態バリエーション、合計3つのバリエーションを作りました。
もし赤いボタンがあったら、または緑のボタンがあったら、例ではコンテンツに応じてサイズが変わるボタンとなっていますが、固定幅サイズのボタンがあったら、と考えると、コードには無駄な部分が多いと言わざるを得ません。私の経験から言うと、間違いなくそれらのバリエーションは開発のどこかのタイミングで発生します。

それではこのコーディングのどこに問題があるのか、抽象化の作業を順を追って見ていきながら説明していきましょう。
まずはクラス名.login

HTML5の仕様書のクラスに関するセクションでも:

・・・著者はクラス属性をコンテンツに求める見た目を説明する値ではなく、 コンテンツ自体の特性を説明する値を利用すること。

とあるように、セマンティックであることをベストプラクティスであるとしています。

クラス名.loginは確かにセマンティックであると言えますが、.logoutボタンがあったとして(ほぼ確実にあるはずですが)、それら2つの要素に共通するパターンはないでしょうか?ユーザもその直接的に関連する2つの要素に対して一定のパターンを期待するはずです。
しかし、コンテンツ派生のセマンティック性にこだわったクラス名を付けてしまうことで、その2つの要素には共通のコードを存在させることは重複したコードを記述することを意味してしまいます。
もちろん、CSSプリプロセッサのSassやStylusにあるextendという機能を使ってこの問題の回避を行うことは可能です。しかし同様にCSSが元々持っている機能でも回避することも出来ます。

単純に.buttonクラスに共有するコードを定義すればよく、バリエーションを2つ目のクラスで定義しすることでも問題を解決できます。

.button.login.button.logoutのようにすることで、.login.logoutが持つ共通のコードは.buttonに担わせることが出来るわけです。
先ほどの.loginボタンから.bottonのベーススタイルを分離してみましょう。
.button {
  padding: 10px 15px;
  background: #808080; /* デフォルトカラーとしてグレーを定義 */
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  text-shadow: 0 –1px 0 rgba(0, 0, 0, 0.4);
  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 1px 1px rgba(0, 0, 0, 0.2);
  -webkit-transition-duration: 0.2s;
  -moz-transition-duration: 0.2s;
  transition-duration: 0.2s;
  -webkit-user-select:none;
  -moz-user-select:none;
  -ms-user-select:none;
  user-select:none;
}

.button:hover {
  text-decoration: none;
}

.button:active {
  -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
  -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
}

今回ベースとなるコンポーネントは色の違いによって拡張されるという定義にしています。.loginボタンが青だとしたら、それに関連するスタイルのみを定義すればよいことになります。こうすることで.logoutボタンの拡張も簡単に行うこと出来ます。
DRYであり、拡張性も高く、メンテナンス性にも優れたアプローチだといえるでしょう。


3バリエーションのボタン例


ウェブサイトが変わってきたのであれば、ワークフローにも転換が必要

今回は例としてボタンというもっともわかりやすい例を取り上げましたが、ウェブサイト、あるいはウェブアプリケーションにはボタンのほかにも様々な再利用できる要素があります。
それら要素をコンポーネント化し、コード化するのにもっとも優れた方法がコンポーネントリストページを作成することです。

HTMLもバージョンが変わり、CSSも同じようにバージョンが変わりました。JavaScriptも初期のウェブに見られた単純なトリック的な要素を扱う言語から、アプリケーション言語として転換を果たしています。しかし、ウェブサイトを制作するフロントエンドのワークフローそのものは大きく変わっていないのが現状です。

ウェブサイトを支える技術に転換があったのであれば、それに即したワークフローが必要だと考えています。

その1つがコンポーネントリストページです。
コンポーネントリストページとは簡単に言うと、ウェブサイト、ウェブアプリに必要なコンポーネントを一枚のページにまとめてコーディングしたものです。

メールマガジン発行ツールを提供しているMailChimpの例(画像の例ではありますが)がその好例です。

スタイルガイドというように呼ばれることも多いですが、この記事ではコンポーネントリストページと呼ぶことにします。ガイドラインというとどうしても作る方も見る方も構えてしまいますが、リストページであれば、単なるリストとしてとらえて考えることが出来ると思うからです。(最終的にスタイルガイドとよばれるドキュメントになっていくのですが。)

デザイナからベクターデータが届いたら、まずそのデータ通りにページをコーディングするのではなく、コンポーネントリストページにコーディングをしていきます。
初期の段階ですべてのパターンや拡張性について考える必要はありません。文章を書くのと同じで、まずは考えていることを書き出してしまうことが大切なことであり、文章と同じく推敲を重ねることがパターンとしての抽象化を適切に行うもっとも効率的な手段です。

アーネスト・ヘミングウェイ氏が言うとおりに『第一草稿はどんなものでもいまいちである』なのです。

すべての要素を1ページに収めてコーディングを行うことにはいくつかの利点があります。

  1. CSSの結合時に問題が起こらない。1ページにすべての要素があるわけですから当然ですが、ページごとにコーディングを行うことに比べて、クラス名の衝突などが起こらないため、仮にCSSファイルが分離されていても問題なく結合することが出来る。HTTPリクエスト数を減らすことはページロード速度の最適化にとって基本となっていますので、思っている以上に効率がよい方法です。

  2. CSSのテストが可能なページを作れる。どんな熟練の開発者でもCSSのカスケードを完璧に制御することは出来ません。どんなプログラム言語でも、実装に伴うテストの手法が確立されているものですが、CSSではなかなか難しいのが現状です。コンポーネントリストページを作ることで、その『当たり前』をCSSに取り込むことが出来ます。

私自身このコンポーネントアプローチを数年実践して来ていますが、すべてのパターンを見いだすことは当然出来ませんし、今後それらのパターンを瞬時に見極められるとも思いません。

まずページとしてコード化する前に、コンポーネントリストというプロトタイプを作ることで、『ああ、またこのパターンがあるのか、でも面倒だからこのままでいいか』という部分を減らすことが出来ると思います。

ウェブサイトはもはやページの集合体ではなく、様々な機能を有するシステムとして捕らえるべき存在です。ウェブページがシステム全体を構成する1つの機能であるとしたら、そのウェブページを構成する要素はシステムを構成するために必要不可欠なパーツとなります。
パーツのみを上手にデザインできてもシステムとして正しく動作するかは保証されませんし、機能だけが単品で存在していてもシステム全体としてのフローの中に組み込まれなければ意味をなしません。

最後に、これからのフロントエンドデベロッパにはどんなパーツを作る方法を知っているか、というトリビア的な技能だけではなく、どんなパーツが手元にあり、それらをツールとしてどう活かしているかが求められると考えています。
上手に抽象化されたコンポーネント/パターンはどんな種類のウェブサイト、ウェブアプリケーションにも適応することが出来ます。ある要素を『どう作るか』よりも、より本質的な『何が問題となっているか』そして、『どう解決するか』にフォーカスをすることがよりよいサービスを作るために必要な質問ではないでしょうか?
そんな質問をし始めるのに必要なきっかけがこのコンポーネントアプローチという考え方だと思っています。ものすごく概念的な話に終始してしまいましたが、手元の作業に集中するだけではなく、全体を俯瞰して開発を考えるきっかけになれば幸いです。

参考リンク