RSCSS という CSS 設計について

(追記)

実践例あった

http://ricostacruz.com/rscss/index.html

追記終わり。


https://github.com/rstacruz/rscss

しばらく CSS とか追ってなかったので、触るにあたって「むやみにCSS書いてたら後で確実に死ぬし、そういえばなんかOOCSSとかあったな」と思っていろいろ調べてたら OOCSS の他にも SMACSS とか BEM とか SuitCSS とか FLOCSS とかなんかいろいろ出てきて大変でした。たしか SMACSS くらいまでは記憶があるんだけど…。

で、どうもどれもしっくり来ないのでさらに調べてみると RSCSS というものを発見。「フレームワークじゃなくてあくまでもアイデア集だよ」ってことらしく、ルールガチガチでもないしなんだかよさ気。

で、RSCSS の README を読みながらメモしたんでせっかくだし公開します。ほぼただ訳しただけなんですが…。英語あまり得意ではないので間違いなど指摘いただけると助かります。

Components

検索フォームのようなひとかたまりをひとつのコンポーネントとして考える

Components の命名規則

Components は ダッシュで区切られた 少なくとも2つの単語 からなる。

  • Like ボタン ( .like-button )
  • 検索フォーム ( .search-form )
  • ニュースカード ( .article-card )

Elements

Elements とは Component を構成する内部要素。

Elements の命名規則

それぞれの Component は通常、複数の Element を持つ。 Element の名前は 1単語 にする。

  .search-form {
    > .field { /* ... */ }
    > .action { /* ... */ }
  }

Element のセレクタ

可能な限り子セレクタ > を使う。Componentのネスト防止になるし、子孫セレクタよりパフォーマンスがいい。

  .article-card {
    .title     { /* これでもいいけど */ }
    > .author  { /* ✓ こっちのほうがいい */ }
  }

複数の単語が使いたい

2つ以上の単語からなる名前をつけたい場合は、ダッシュやアンダースコアを使わないで単純に連結する。

  .profile-box {
    > .firstname { /* ... */ }
    > .lastname { /* ... */ }
    > .avatar { /* ... */ }
  }

タグセレクタを避ける

可能な限りクラス名を使用する。タグセレクタはよいものだが、若干のパフォーマンス上のペナルティがあり、また説明的ではないかもしれない。

  .article-card {
    > h3    { /* ✗ これは避ける */ }
    > .name { /* ✓ こうするほうがいい */ }
  }

Variant

Component も Element もそれぞれ variant を持つことができる。

Variant の命名

variant のためのクラス名にはダッシュ ( - ) を prefix としてつける

  .like-button {
    &.-wide { /* ... */ }
    &.-short { /* ... */ }
    &.-disabled { /* ... */ }
  }

Element の variant

Element もまた variant をもつことができる

  .shopping-card {
    > .title { /* ... */ }
    > .title.-small { /* ... */ }
  }

ダッシュプレフィックス

ダッシュは variant のプレフィックス(接頭辞)として使用できる

  • Element との曖昧さ防止
  • CSSのクラス名はアルファベットそして _ または - で始めることができる
  • ダッシュはアンダースコアよりタイプしやすい
  • it kind of resembles switches in UNIX commands ( gcc -O2 -Wall -emit-last)

ダッシュやアンダースコアではじまるクラス名ってOKなの知らなかった)

In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, the identifier "B&W?" may be written as "B&W\?" or "B\26 W\3F".

http://www.w3.org/TR/CSS21/syndata.html#characters

ネストした Component

<div class='article-link'>
  <div class='vote-box'>
    ...
  </div>
  <h3 class='title'>...</h3>
  <p class='meta'>...</p>
</div>

時には Component をネストする必要がでてくる

ネストした Component の Variant

Component は何らかの方法で他のComponentにネストする必要が生じるかもしれない。ネストしたコンポーネントを、それを内包するコンポーネントに突っ込むような変更は避ける。

.article-header {
  > .vote-box > .up { /* ✗ これは避ける */ }
}

かわりに、ネストした Component に Variant を追加し、それを含む component からそれを適用するのが好ましい。

<div class='article-header'>
  <div class='vote-box -highlight'>
    ...
  </div>
  ...
</div>
.vote-box {
  &.-highlight > .up { /* ... */ }
}

ネストした Component の単純化

しばしば、ネストしたコンポーネントマークアップが汚くなる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='search-button -red -large'></button>
</div>

CSSプリプロセッサーの @extend によって単純化することができる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='submit'></button>
</div>
.search-form {
  > .submit {
    @extend .search-button;
    @extend .search-button.-red;
    @extend .search-button.-large;
  }
}

Layout

ポジションに関するプロパティを避ける

Component は異なるコンテキストで再利用できるべきなので、以下のようなプロパティを書くのを避ける

  • Positioning (position, top, left, right, bottom)
  • Floats (float, clear)
  • Margins (margin)
  • Dimensions (width, height) *

固定寸法

アバターやロゴのような width/height が固定される要素については例外とする

親要素でポジションを定義する

これらを定義したいなら、それらが所属するコンテキストに定義する。下記の例で言うと、 list コンポーネントに適用するのであって、 .article-card 自身に定義するのではない。

  .article-list {
    & {
      @include clearfix;
    }

    > .article-card {
      width: 33.3%;
      float: left;
    }
  }

  .article-card {
    & { /* ... */ }
    > .image { /* ... */ }
    > .title { /* ... */ }
    > .category { /* ... */ }
  }

Helpers

._unmargin { margin: 0 !important; }
._center { text-align: center !important; }
._pull-left { float: left !important; }
._pull-right { float: right !important; }

汎用クラスはアンダースコアで始まる名前で別ファイルに置かれ、値を上書きする。通常、 !important でタグ付けされる。慎重に使うこと。

Helper の命名

アンダースコアをクラス名のプレフィックスとして付ける。これは component に定義された modifier と容易に見分けることができる。アンダースコアはちょっと不格好で内部的な副作用がある。Helperの使い過ぎには嫌気が差すだろう。

  <div class='order-graphs -slim _unmargin'>
  </div>

Helper の整理

helpers というひとつのファイルに収める。複数ファイルに分割することもできるが、たくさんの helper を最小限に維持するのが好ましい。

CSS structure

ファイル単位でひとつの Component

それぞれの Component はそれぞれひとつのファイルに収める

  /* css/components/search-form.scss */
  .search-form {
    > .button { /* ... */ }
    > .field { /* ... */ }
    > .label { /* ... */ }

    // variants
    &.-small { /* ... */ }
    &.-wide { /* ... */ }
  }

Use glob matching

 @import 'components/*';

過剰なネストを避ける

できるだけ1階層のネストにとどめる。ネストし過ぎは簡単に迷子になる。

  /* ✗ Avoid: 3 levels of nesting */
  .image-frame {
    > .description {
      /* ... */

      > .icon {
        /* ... */
      }
    }
  }

  /* ✓ Better: 2 levels */
  .image-frame {
    > .description { /* ... */ }
    > .description > .icon { /* ... */ }
  }

落とし穴

ネストした Component で死亡

ネストした Component が似たような名前の Element を持つ場合に気をつける。

<article class='article-link'>
 <div class='vote-box'>
    <button class='up'></button>
    <button class='down'></button>
    <span class='count'>4</span>
  </div>

  <h3 class='title'>Article title</h3>
  <p class='count'>3 votes</p>
</article>
.article-link {
  > .title { /* ... */ }
  > .count { /* ... (!!!) */ }
}

.vote-box {
  > .up { /* ... */ }
  > .down { /* ... */ }
  > .count { /* ... */ }
}

この場合、 .article-link > .count> セレクタを持っておらず、おそらく .vote-box .count に適用される。これは子セレクタが好ましい理由のひとつ。

懸念事項

  • ダッシュとか最悪: このルールを無視して普通の単語をつかってもいい。ただ、 Component - Element - Variant という考えは忘れないで欲しい

  • 2単語なんて思いつかない: alert のような、いくつかの Component は表現上の理由から1単語であることを要する。これらはサフィックス(接尾辞)を使用することでブロック要素だということをより明確にすることを検討したほうがよい。

  • .alert-box

  • .alert-card
  • .alert-block

Or for inlines:

  • .link-button
  • .link-span

その他のリソース

その他のソリューション

BEM

BEM は素晴らしいけどシンタックスがちょっとキモいという人もいると思う。RSCSSはBEMとはシンタックスが違うだけで、慣習にはほぼ従っている。

<!-- BEM -->
<form class='site-search site-search--full'>
  <input  class='site-search__field' type='text'>
  <button class='site-search__button'></button>
</form>
<!-- rscss -->
<form class='site-search -full'>
  <input  class='field' type='text'>
  <button class='button'></button>
</form>

用語

同様のコンセプトはその他のCSS設計にもみられる

RSCSS BEM SMACSS
Component Block Module
Element Element Sub-Component
Layout ? Layout
Variant Modifier Sub-Module & State

まとめ

CSSのvertical-alignについて

大前提として、僕の知識は基本的にCSS2止まりです。また、Webデザイナーでもありません。今は(css3では)こうするのがトレンドだよーとかあれば教えて頂けると大変喜びます。

昨夜twitter403 Forbiddenという記事を紹介してもらった。確かに昔ハマったことあるなーと思いつつvertical-alignで検索しようとしたら「vertical-align 効かない」が候補に出てきた。結構みんなはまってるんですね。

vertical-alignはblock要素には適用できない

まず最初にハマるポイント。こいつはinline, inline-block, table-cellに対してしか効かない。block要素には適用することができない。
昔懐かしいtableレイアウトの感覚で

div#hoge { vertical-align: middle; }

なんてしても全く意味が無い。「vertical-align 効かない」でググってるのは大抵このパターン。

具体的に見ていく

インライン要素
baseline インラインボックスのbaseline(無ければその下辺を)親ボックスのbaselineに揃える
middle インラインボックスの中心を親ボックスのbaselineから0.5ex上げる。(0.5exは小文字の"x"の半分の高さ)
sub インラインボックスのbaselineを親ボックスの下付き文字として適切な位置まで下げる。
super インラインボックスのbaselineを親ボックスの上付き文字として適切な位置まで上げる。
text-top インラインボックスの上辺を親要素のfontの下辺に揃える
text-bottom インラインボックスの下辺を親要素のfontの上辺に揃える
top インラインボックスの上辺を行ボックスの上辺に揃える
bottom インラインボックスの下辺を行ボックスの下辺に揃える

具体的にはこう。
f:id:kk6:20111218162111j:image

また、これには長さやパーセンテージを指定することも出来る。

長さ 指定した長さの分だけボックスを上げ下げする。(マイナス値で下がる)0を指定すればbaselineを指定したのと同様の効果。
パーセンテージ line-heightに対して指定した%だけblockを上げ下げする。(マイナス値で下がる)0%を指定すればbaselineを指定したのと同様の効果。

具体的にはこうなる。

f:id:kk6:20111218162120j:image

親ブロックの上辺が押し上げられちゃってますね。

ちなみにあたり前のことだけど、基準となる親要素が存在しなければvertical-alignを指定しても効果は得られない。

セル要素

f:id:kk6:20111218162056j:image

なんか昔と挙動が違うんですけど。
上から順に

  • 1行目:最初のセルには何も指定していない
  • 2行目:最初のセルにvertical-align: baseline;を指定
  • 3行目:最初のセルにvertical-align: bottom;を指定

昔は1行目の2番目のセルは最初のセルのフォントのベースラインに揃えられてたと思うんだけどなぁ。2行目は最初のセル自体にvertical-align:baseline;を指定してみた。すると行のbaseline自体が書き換えられた模様。最初のセルにbaselineを指定していない場合のベースラインはCSSを一切適用していない時点(規定のフォントサイズの時)のベースラインなのかなー、といった結論に至った。

というわけでまとめるとこんな感じかな。

baseline 指定したセルをそれを含む行の最初のセルのベースラインに揃える。
middle 指定したセルをそれを含む行の中心線と揃える
top 指定したセルをそれを含む行の上辺に揃える
bottom 指定したセルをそれを含む行の下辺に揃える

[sub, super, text-top, text-bottom]はセル要素には適用できない、と説明してるサイトが多いけど、それだけだとちょっと説明不足で、正しくは「適用されない代わりに既定値であるbaselineを適用する」。vertical-alignをそもそも指定しないのと、セルに対して適用できないsubなどを指定するのとでは挙動が違うのです。

じゃあブロック要素の垂直位置を真ん中に指定した場合はどうするの

position要素でblockを相対配置する

display: table-cell;とする、なんて記事も見かけたけど、表に全然関係ない要素にこれを指定するのはなんだか気持ち悪い。

line-heightの値を親blockのheightの値と揃える、なんていうのもなんとなくバッドノウハウな気が。内包するinline要素でvertical-alignを長さで指定した場合、inline要素のline-heightが変わっちゃうだろうからおかしなことになるんじゃないかな。試してないけど。

block要素の配置には基本的にposition, top, right, bottom, leftそれぞれを指定するというのが昔僕が学んだ方法なんだけど、今のトレンドはどうなんだろう。
ちなみに、これにもちょっとした罠がある。相対配置にする場合はposition: relative;を指定してやるんだけど、親blockとの相対位置ではなくて、自身の本来の位置からの相対位置なので 例えば内側のdiv要素(仮にid="inner"を指定していたとする)にtop: 50%; としても、#innerの上辺が真ん中に来てしまい、ブロックそのものは真ん中よりちょっと下に見えるだろう。なのでまあ表示をみながらちょうどいい数値を探るしかないですよね。

でもきっと今はもっとスマートな方法があるに違いない!
...あるといいな。

Googleのナビゲーションバーをページ上部に固定するユーザースタイル

f:id:kk6:20110703033206p:image
完成してから気づきましたが、すでに Google+ 限定でなら書いてる人も何人かいるみたいですね。僕はGoogleの主要なサービスにほぼ全てで、上部のナビゲーションバーを固定させてみました。かなり面倒だったので後悔してますが。

Gmailは元々「アーカイブ」とか「迷惑メール」とかのボタンが配置してあるバーがfixedになってて、ページのスクロールに合わせて付いてくるんですが、これとナビゲーションバーの間隔も調整して重ならないようにしてます。
f:id:kk6:20110703033207p:image

あとはナビゲーションバーを透過させてbox-shadow効かせて、ちょっとだけモダンぶってます。

ソースはGistとuserstyle.orgに置いてますのでお好きな方からどうぞ。

もしおかしなところがあったら教えていただけると嬉しいです。

ちょっとだけnth-child擬似要素について解説

ちなみにGmailのHTMLがちょっと厄介で、だいたい以下のようになってます。

<body class="cP">
  <div>
  /* 中略 */
    <div class="vI8oZc cS">
    <div class="nH">
      <div class="nH GcwpPb-Z8OBDd">
        <div class="nH oy8Mbf qp">
        <div class ="nH">
          <div class="no"> /* これに適用させたい */
          <div class="dJ"></div>
        </div>
      </div>
      <div class="nH">
        <div class="no"> /* これには適用させない */
        /* 以下略 */

「no」というクラス名がいくつかあって、そのうち最初のほうにだけスタイルを適用させたい。そんな時はCSS3のnth-child()擬似要素を使用します。これってどっちが先なのかは知りませんがjQueryにもありますね。以下のように使います。

body.cP div:nth-child(1) .nH:nth-child(1)
.nH:nth-child(1) .no:nth-child(1) {
    margin-top: 30px;
}

この nth-child は他にも

.class-name:nth-child(odd) /* 奇数番目の要素すべて*/
.class-name:nth-child(even) /* 奇数番目の要素すべて */
.class-name:nth-child(5n+1) /* 5つを1グループとして、各グループの1番目の要素すべて */
}

のようにも使える割と便利な機能です。

ああ、そうだ。

当然ですがIE6〜8まではCSSのnth-childに対応してません。
おとなしく Firefox, Google Chrome, Opera あたりに乗り換えてください。