Classy CSS: Sassスタイルシートへのプログラマティックアプローチ

この記事を書き始めたのは、現在使われているCSSの命名規則やスタイルの融合についての見解をAtomic OOBEMITSCSSという題名で風刺的な記事にまとめ、SitePointに投稿した数週間後です。それが8月頃のことだったのですが、この投稿はその後の私の生活に影響を及ぼしました。冗談のつもりで「Atomic OOBEMITSCSS」という題名をつけたがために、世間の人々はその題名を取り上げ、話題にしたのです。(正直言って、その内容について直接人々に質問することは、私にとって非常に愉快なことでした)。そして今年のSassConfで@extendの利用について議論したことがきっかけで、この見解を再検討する必要性に気づきました。

Classy CSSについて

上記で紹介した記事(「Atomic OOBEMITSCSS」)では、コンポーネントをマークアップする方法について(Pinterestを使った例を示しながら)説明し、それぞれのスタイルの関連性について比較しました。私たちのフロントエンドアーキテクチャは大規模なパターンライブラリ上で耐性テストを行っていますが、そのことで何か質問された時には、いまだに私はその記事を手ごろなマニュアルとして使っています。


注釈:SitePointの記事に載せた例の1つ

その結果、従来のコンピュータサイエンスの経験がある人々に対して、私はいつの間にか、システム(そしてSass自体)を説明する必要が出てきたことに気づきました。そうしていくうちに分かったのは、記事の中で説明したCSSのアーキテクチャシステムがどれほどプログラマティックでクラスに基づく考え方をしているかということです。そこで、少し時間を取って、その考え方をこれからも使っていくのにふさわしい名前をつけてみました。Classy CSSです。では、従来の考え方をするプログラマにとって、もう少し身近に感じられるような方法で、この考え方について説明していきたいと思います。

この記事の目的は、以下の3つです。
1. @extendについて擁護すること
2. Classy CSSを紹介すること(Atomic OOBEMITSCSSより言いやすい名前ですね)
3. CSSの、スケーラブルかつモジュラー、そしてクラスに基づいたアプローチについて説明すること

Classyなボタン

それでは、例としてボタンを使いながら、どのような仕組みになっているのかについて詳しく見ていきましょう。(ボタンを使う理由は、CSSにおける”Hello World”の役目をボタンが果たしているからです)。

buttons
まず、すべてのボタンのベースとなるコードを含む、暗黙のプレースホルダセレクタから見ていきましょう。BEM風の構文を修正したものを使います。命名の最初の部分は、これから参照していくオブジェクト(buttonまたはbtn)です。ダッシュを2つ置いたあとにはモディファイア(今回の場合はbase)を記述します。注意: 変数も同じ方法で命名します。変数の型を基にするのです。(以下の例ではcolorがそれに該当します)。

$color--primary: #b29;
$color--secondary: #19d;

%btn--base {
  border: 1px solid currentColor;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;

  &:hover {
    color: white;
    background: black;
  }
}

上記でbaseとして定義したbuttonが、私たちの使うbuttonクラスです。これをベースにして各ボタンを構築していきます。つまり、それぞれのボタンはこのクラスのインスタンスです。各ボタン要素はbutton型で、いくつか共通の特性(solidの輪郭や透明な背景など)を持っています。各ボタンから共通のプロパティを呼び出し、それをベースにして構築していくのです。プロパティの上書きはしませんが、プロパティを追加することができます。すべてbuttonの上に成り立っているものであり、基となるbuttonを継承します。(この説明で十分でしょうか?)

protoypal inheritance
注釈:「知られざるJavaScript: thisとオブジェクトプロパティ(You Don’t Know JS: This & Object Prototypes)」から引用した原型的な継承の実例

%btn--primary {
  @extend %btn--base;
  color: $color--primary;
  font-size: 1.5em;
}

%btn--secondary {
  @extend %btn--base;
  color: $color--secondary;
  font-size: 1.1em;
}

以下のように使います。

.hero__btn {
  @extend %btn--primary;
  margin: 2em;
}

.sidebar__btn {
  @extend %btn--secondary
}

.global-nav__btn--login {
  @extend %btn--secondary;
  margin-right: 1em;
}

これらの記述をもっとclassyな方法で書くとすると、コンポーネント(メインビジュアル(hero)、サイドバー、グローバルナビ)ごとに、部分的な.scss fileを持つことになり、ここで、実際に使うクラスのインスタンスが生成されます。具体的には、以下のようになるはずです。

_hero.scss

.hero {

  ...

  &__btn {
    @extend %btn--primary;
  }

  ...

}

_sidebar.scss

.sidebar {

  ...

  &__btn {
    @extend %btn--secondary;
  }

  ...

}

_global-nav.scss

.global-nav {

    ...

    &__btn {
      @extend %btn--secondary;

      &--login {
        @extend .global-nav__btn;
        margin-right: 1em;
        // ここで
        // .global-nav__btn--loginのスタイリングをします
    }
  }
}

それでは、Mixinの利用は?

なぜ、代わりに@mixinを使わないのか疑問を持つかもしれませんね。クラスを分けているので、@extendを使う方が目的やコンセプトに合っています。システムを構築するために継承したプレースホルダのクラス内にMixinを作成することは可能です。そして、これらのインスタンスをクラス内に生成するために@extendを使います。

@extendを守る

@extendを使うことで何が起きているか(=スタイルの複製を作る代わりに追加する)を理解できていれば、、より小さいアウトプット用CSSファイルと、より簡潔なコードにすることができます。「gzip圧縮してしまえばそんなことは問題にならない」との主張も可能ですが、サーバの環境設定へのアクセスがなければ、確実にgzip圧縮できるかどうかわからない場合もあります。

@extendを理解し、効果的に使うには、正確な“現状”@mixin@extendの根本的な違いを位階することが大切です。以下のイメージ図をご覧ください。

@mixin


@extend


@mixinはスタンプのようなものです。与えられた引数(選択可能)と共に、プロパティブロックの複製バージョンを生成します。@extendは、継承する要素をプロパティブロックに追加します。これは”yes, and ___” 命令となります。

つまり、実装の度にコードブロックを再作成しないため、より小さいアウトプット用CSSファイルができるということです。@extendを使うと、単純にプロパティを参照できるようになります。ここではExtendの方がぴったりですね。論理的な目的が理解できます。
#teamExtend

これの欠点は、「あるプロパティを持つコードブロックを含む新たなインスタンスを呼び出す」というやり方ではなく、単純に継承するスタイルを参照するだけなので、プロパティのブロックをどこに置くかコントロールできないことです(これがmediaクエリ内で@extendを使えない理由です)。しかし、Classy CSSのルールに従えば関係ありません。静的プレースホルダをインスタンス化するために、実際に使えるクラスへと継承します。また、@mixinはある目的のため、まだ使わずにおきます。それは、「静的プレースホルダ内で使うことで継承されるコードを構築する」という目的です。

Classy CSSにおけるMixinはコンストラクタのようなものです。静的プレースホルダのセレクタ内でのみ使うことができます。

例として、1つ目のボタンと2つ目のボタンを簡単に作れるように、ボタンのMixinを作ってみましょう。

// 引数の後のコロンはデフォルト引数を指す
// コンストラクタ関数の作成(mixin)

@mixin btn-me($color: hotpink, $size: normal) {
  border: 1px solid $color;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;

  @if $size == 'small' {
    font-size: .8em;
  } @else {
    font-size: 1.2em;
  }

  &:hover {
    color: white;
    background: $color;
  }
}

// 継承/参照のためのプレースホルダとなるクラスを作成

%btn--primary {
  @include btn-me; // 引数がないのは、デフォルト値を取ることを意味する
}

%btn--secondary {
  @include btn-me(blue, small)
}

// セマンティックな名前でコードをインスタンス化
// ここは、コンパイルされるようなコードを書く
// 唯一の個所になります
.hero__btn {
  @extend %btn--secondary;
}

CSSのアウトプットは次のようになります。

.hero__btn {
  border: 1px solid blue;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;
  font-size: .8em;
}

.hero__btn:hover {
  color: white;
  background: blue;
}

有益性

これが独断的なシステムであることは言うまでもありません。また、初心者にとっては複雑でしょう。しかし使い始めてみると、名前付けや構築的な決断をする際に、非常に役立ちます。

有益性には以下も含まれます。

  • 組織化されたコードを、特定のクラッシュやオーバーライド(ITCSSをご覧ください)が絶対に起こらないようにできる。つまりCSSを簡素化、効率化し、より小さいCSSファイルをアウトプットできる。

  • 矛盾しない命名規則(BEM)により、チーム間で同一化し、混乱を減らすためのリファレンスを提供できる。

  • システムを組織化し、メンテナンスやスケーリングを可能にする。

classy(上品)に。そしてSassy(生意気)に。

では、これでおしまいです。お見送りはいりませんよ。