
100年後も崩れないCSS勉強会、第2回です!というわけで、きょうはコンポーネントについてお話しさせてください。

突然ですが、こちらはなんのへんてつもない Amazon のページです(2015年9月29日現在)。このページをよく見てみると、同じような見た目のものがあると思いませんか?

例えば、ヘッダーのメニューのなかの、右に小さな矢印がついたリンクとか…

例えば、サイドバーのカテゴリーの見出しと各項目が一覧になっているリンクとか…

本の書影とカテゴリーの見出し・本のタイトルが一緒になったリンクもあります。

さらには、ページのなかでオレンジ色の太字の見出しなんかも、文字の大きさは違うけど同じ見た目ですね。

こんなふうに、ウェブページはたくさんの要素があるように見えて、案外おなじ見た目の要素を繰り返したり、組み合わせることで作られています。

このような共通した要素のことを、ここではコンポーネントと呼びたいと思います。

「共通している」ということを言い換えると、「ここだけではなく他の場所でも再利用できる」ということでもあります。

それでは、さきほどの Amazon のオレンジ色の見出しの部分をコーディングするぞ!となったら、どんなふうに書くでしょうか。

例えばこんなふうにそれぞれ class 属性に名前をつけて、スタイルを指定したとします。でも、この3つをよく見ると、文字の色や太さの指定はどれも共通していて、違うのは文字の大きさだけですね。

じゃあ、ということで共通しているスタイルは .title
という class セレクタのスタイルとして指定しておいて、それぞれの文字の大きさだけ class セレクタを追加して指定するようにしたら、どうでしょうか。こう書くと、文字の色や太さを変えたくなったときも、1行変えるだけで一気に変更できるので楽なのではないでしょうか。

この楽をしたいという怠惰な考えをもっと進めると、.page
だとか .section
だとかっていうのは「場所の意味」を指しているわけですが、どうせこのスタイルでは文字の大きさしか指定していないので、large だとか medium といった「見た目の意味」で指定できるようにするのは、どうでしょうか。こう書くと、ページの下のほうで 16px の見出しをつけたい!って思ったとき、その要素がどんな意味を持つ場所にあったとしても、HTML で .medium
と class を追加するだけでスタイルを指定できて便利じゃないですか?

しかし、この名前のつけかたには問題があります。というのも、CSS はスタイルを適用する範囲を指定できないからです。ここでは .large
という名前をつけていますが、他の場所でも大きい要素を表現したい場合に .large
という名前をつけることも当然あると思います。もちろんあとから書いた .large
は .title.large
に影響するので、ぜんぜん想定していない見た目になってしまいます。.large
とかいう競合しそうな名前をつけないように気をつければいいって話なんですけど、現実的に意図しない見た目になっちゃたよオイというのは CSS を書いていて「あるある」なできごとですよね。

じゃあどうすればいいのか? ひとつの解決策としては、CSS の仕様が足りないのであれば自分たちで工夫しようということで、.title-large
といった名前にするといいんじゃないかという考え方があります。こう書いておけば、おもむろに誰かが .large
って書いても影響を受けないので安心だし、いままで .large
だけだと何についてのスタイルなのかわからなかったものが、.title
の見た目を指定しているスタイルシートであるということと、.title
の文字の大きさを指定しているスタイルシートであることが、名前から伝わってくるように思います。

CSS 設計業界(?)では、このような考え方を説明するときに BEM(ベム)(ロシア発祥)という概念を利用することがあります。BEM では、ここでいう title
のことを「Block」と呼んでいて、

large
とか medium
といったバリエーション違いを表している名前を「Modifier」と呼んでいます。

こちらはなんのへんてつもない検索フォームです。form 要素の .search-form
のなかに、テキストフィールドの .search-form-textfield
と、検索ボタンの .search-form-button
という要素が入っています。

BEM(ベム)(ロシア発祥)では、ここでいう search-form
を「Block」と呼んでいて、

こんなふうに search-form
の中に入っている textfield
や button
といった要素を「Element」と呼んでいます。BEM(ベム)(ロシア発祥)は、「Block」「Element」「Modifier」の頭文字を略した名前なんですね。

でも、こんなふうに3種類の概念がひとつの名前に混ざっていると、いったいこの名前は Block なのか Element なのか Modifier なのか、どれを指しているのかわからないですよね…?

そこで、BEM の概念を理解しやすいように、MindBEMding(マインドベムディング)という命名規則が考え出されました。Element の前にはアンダーバー2つ、Modifier の前にはハイフン2つをつけることにしようじゃないか。これで Element と Modifier の区別がつく!やったー!

さきほどの検索フォームを MindBEMding の命名規則を用いて書いてみたら、このようになるでしょうか。結果的にまぁ名前がめっちゃ長くなっちゃうわけですが、でも、textfiled
や button
が search-form
のためにある要素だって、わかりやすくなったのではないでしょうか。

巨大な検索フォームを作る必要があっても大丈夫。large
という Modifier がついたときのスタイルを指定すればいいんです。意味と見た目を分離して、どこでも再利用できる部品として定義する。同じ記述はできるだけ共通化して、全体を把握しやすくする。仕事も速くなって、リニューアルもかんたんです。コンポーネント、最高!

こんなふうな命名規則や考え方、またディレクトリ構成やファイルの分け方などはいろいろと提唱されていますが、どれもコンポーネントを意識して、いちど書いたスタイルの再利用性を高める、という視点は同じです。MindBEMding のハイフン2つやアンダーバー2つといった命名規則だって別に強制されているわけではなく、壊れにくい CSS を書くためのヒントとして、どう書いたっていいし、なによりチームメンバーや半年後のすべてを忘れた自分に伝わればいいんじゃないかなと思います。

さて、こちらはなんのへんてつもないタブのメニューです。クリックするとページに移動して、選択したタブの色がオレンジ色に変わるよくあるやつ。このスタイルがページのなかで何度も登場するので、業を煮やしてきょうはこれをコンポーネントにしようと思います。いったいどうやって書いたらいいでしょうか?

1案としては、.global-menu
を Block の名前として、そのなかにあるやつを .global-menu
からはじまる名前にして .global-menu-item
にする。そうすれば、.global-menu
関連の要素なんだなっていうのが伝わりますね。そして、選択中のタブには .active
って class をつけることで区別する。

いや、当プロジェクトでは Modifier は先頭に .is-
をつけております、とか。

いや、そもそも .global-menu
っていうけど、グローバルメニュー以外でもこの見た目あるよねってことで、どこでも使えるように意味ではなく見た目をあらわす .tab-menu
って名前にしちゃうとか。ハイフンの区切り文字と Element の区別がつかないから、MindBEMding を取り入れてみるとか。

いや、俺はアンダーバー派だよっていうか、アプリケーションの他のコードはアンダーバーならアンダーバーにしちゃってもいいし、Modifier はアンダーバー2個からはじめるルールにしちゃうぞっていうのも良いと思います。

いや、.tab-menu
がコンポーネントなのかどうかっていうのはよくわからないから、もっとわかりやすくするために先頭に .c-
をつけるよ、みたいな几帳面な人もいます。

というような名前のつけかた、すでに考案されている命名規則は、この「CSS 設計の教科書」というすばらしい緑色の本に詳しく書いてあるので、ぜひ読んでください!

新規開発ならともかく、いま担当してるリリース済みのサービスで運用しながらどうやって導入していけばいいの?っていうことについては、このペパボの GitHub Pages にある「CSS 設計の長い夢」っていうページで @otthi さんが書かれているので、ぜひ読んでください!

まとめです。ページのなかでもし3回も同じ見た目の要素が登場することがあったら、ぜひこれはコンポーネントになるかもしれないって思って書いてみるのはどうでしょうか。きっと、あとで楽になったり便利になったり、壊れなかったりしてカッコいいと思います。ただし、逆になんでもコンポーネント化するとわけがわからなくなってしまうので、3回っていうのは結構よい基準なんじゃないかなっていうのは、やっぱりこの緑の本に書いてありましたので、僕も同意です!

とはいえ、コンポーネントって自分で思ってるだけでは他の人はなんのことかよくわからないですよね。だから、チームメンバーや半年後の自分が把握できるように、スタイルシートの説明書を作るのはどうでしょうか。そんなドキュメントの作り方を、次回はお話ししたいです。よろしくお願いします!