【CSS設計】SMACSSからCSSプリプロセッサとの付き合い方について学んだこと

店長


【CSS設計】SMACSSからCSSプリプロセッサとの付き合い方について学んだこと

こんにちは、店長です。

今回はSMACSSというCSSの設計方法から学んだ、CSSプリプロセッサとの良い関係についてまとめていきます。「SMACSSはなんとなく知ってるけど、どうしたらよいかわからない」「CSSプリプロセッサは使っているけど、うまく使えない」という方の参考になればと幸いです。

なお、CSSプリプロセッサとはCSSを拡張していろんな機能を追加したものを言います。もしCSSプリプロセッサを使ったことがないという方は、こちらの記事を参考にしていただければと思います。

※本記事では、2015年11月18日にいいオフィスで行われた「書籍『最強のCSS設計』出版記念イベント&勉強会―CSS設計、チーム開発を成功させるために必要なこと」で登壇したときの内容にさらに情報を加えてご紹介していきます。イベントのスライドは下記をご覧ください。

まずSMACSSとは

SMACSSとはCSS設計手法の一つです。『Scalable and Modular Architecture for CSS』の頭文字をとってSMACSSと呼ばれています。SMACSSの特徴は5つのカテゴリに分類をして、それぞれの考え方に合わせた設計を行うのが特徴です。

今回の記事では、SMACSSについての解説は割愛させていただきます。SMACSSの詳しい内容については、『Scalable and Modular Architecture for CSS』をぜひ読んでみてください。こちらは日本語に翻訳もされていて、かなり読みやすく参考になるところも多いのでオススメです。

またすぐに学びたいという方はCodeGridさんの記事が参考になるので読んでみてください。
SMACSSによるCSSの設計 – ベースとレイアウト | CodeGrid

もちろん『最強のCSS設計』でも紹介しています。気になる方はぜひ!

CSS設計とCSSプリプロセッサの付き合い方

ここからはCSSプリプロセッサの機能に焦点をあてながら、どのような書き方をするのが良いのかまとめていきたいと思います。

今回はSMACSS × Sassでコードを記述していきます。

入れ子は必要なときだけ使う

smacss2
CSSプリプロセッサでもよく使う機能であろう入れ子。入れ子はクラスの親子関係が把握しやすく便利ですが、不必要に使い過ぎるとメンテナンスしにくいコードになる可能性があります。

具体例

例えば、このようなモジュールが存在したとします。

smacss

<div class="profile">
	<div class="profile-image">
		<img src="photo.jpg">
	</div>
	<div class="profile-body">
		<div class="profile-name"> ~ </div>
		<p> ~ </p>
	</div>
</div>

このモジュールにスタイルを当ててみました。

.profile {
    width: 300px;
    .profile-image {
        > img {
            width: 100%;
            height: auto;
        }
    }
    .profile-body {
        background: white;
        font-size: 16px;
        .profile-name {
            font-size: 24px;
        }
    }
}

入れ子を使って要素の親子関係も把握できるし、なんとなくいい感じですね。
こちらをコンパイルしてみましょう。

.profile {
    width: 300px;
}
.profile .profile-image > img { //深度が深い
    width: 100%;
    height: auto;
}
.profile .profile-body {
    background: white;
    font-size: 16px;

.profile .profile-body .profile-name { //深度が深い
    font-size: 24px;
}

コンパイルされたコードはこんな感じになりました。しかしよく見てみると、出力されたコードの中にはセレクタが多く、くっついてる部分が見受けられます。
とっても便利な入れ子ですが、入れ子をしすぎると以下のような欠点があります。

入れ子を使いすぎたときの欠点

深度(詳細度)が深くなる
深度が深いとはセレクタがいくつも連続したスタイルを指します。深度が深すぎると、一番最初のセレクタに依存をしてしまうため、モジュール内での場所の変更がしづらくなる場合があります。またそのスタイルを打ち消すためにはより多くのセレクタを使用しなければなりません。これは打ち消しのためのクラス・IDを生み、最終的には!importantという最終奥義を使うハメになるので、避けるほうが良いでしょう。
見通しが悪い
入れ子は一見、見やすいように感じるかもしれませんが、やはり深くなりすぎるとコードが追いづらくなります。コンパイルされたコードもセレクタが多くなり、読みづらくなるので、なるべくシンプルな方がよいです。

SMACSSではモジュール内のパーツは、それぞれ用途に合わせたのクラスを振ります。そしてモジュール内のタグに対してスタイルを当てる場合は、中のタグは変わることがないことを前提で当てます。
なので先ほどのスタイルはこんな風に書くとよりスッキリします。

.profile {
    width: 300px;
}

.profile-image {
    > img {
        width: 100%;
	height: auto;
    }
}

.profile-body {
    background: white;
    font-size: 16px;
}

.profile-name {
    font-size: 24px;
}

親のクラスに対して入れ子するのではなく、モジュールのパーツごとに切り分けました。そしてそれに紐づくものみ入れ子にしています。

これをコンパイルするとこんな感じです。

.profile {
    width: 300px;
}

.profile-image > img {
    width: 100%;
    height: auto;
}

.profile-body {
    background: white;
    font-size: 16px;
}

.profile-name {
    font-size: 24px;
}

ほぼコンパイル前と変わっていませんが、入れ子をした時よりも深度は浅くなり、把握しやすく、扱いやすくなりました。入れ子をしなくても命名規則でどのモジュールに属しているかは判断ができるので無理に入れ子にする必要はないのです。

ちなみに私の場合

厳密にルール化をしているわけではありませんが、自分がSMACSSで書く場合は以下の場合で入れ子をすることがほとんどです。

  • モジュールのサブクラスとしてスタイルを記述をする
  • タグにスタイルを当てる(使う際は子セレクタを使う)
  • ステートルールを記述する

深度が深くなればなるほど、拡張性、保守性が低くなるのでなるべく深度を浅くするように心がけています。

@extend、mixinはほどほどに

smacss3
@extend、mixinは頻繁に出てくるパターンを当てる際にとても便利です。ですが使い方を間違えると理解しにくく、単調なコードになりやすいので注意が必要です。

@extendの欠点

@extendは以下のように利用されます。

%shadow {
    box-shadow: 0px 2px 2px rbga(0, 0, 0, .2);
}

.profile{
    @extend %shadow;
}

.media{
    @extend %shadow;
}

これはshadowのスタイルを.profileと.mediaという2つのモジュールに対して使用している例です。
まだこれならわかりやすいかもしれませんが、@extendを使用している箇所が増えるとコンパイル後にこのような形になります。

.profile,
.hero,
.header,
.box,
.side-nav,
.nav,
.tag,
.media,
.author,
.member,
.banner {
    box-shadow: 0px 2px 2px rbga(0, 0, 0, .2);
}

@extendを使用するとこのようにたくさんセレクターが並び、見通しが悪くなります。また、CSSが出力される位置が、@extend元ということもあり、どこで@extendをしているのかわかりづらいという欠点もあります。

mixinの欠点

その点、mixinは@importした場所にスタイルが記述されるので@extendよりも見通しはよく感じます。ただ、大量のスタイルをmixinでまとめて使い回すのは、単調なCSSを大量に書くことにつながります。

以下がその例です。

@mixin circle {
    border: 1px solid #900;
    border-radius: 3px;
    background: #fff;
}
.profile {
    width: 300px;
    @include custom-border;
}
.media {
    padding: 20px;
    @include custom-border;
}
.article {
    background: #f7f7f7;
    @include custom-border;
}

上記のようにmixinを使ってみました。このときのコンパイルされたCSSに注目しましょう。

.profile {
    width: 300px;
    border: 1px solid #900; //同じコード
    border-radius: 3px; //同じコード
    background: #fff; //同じコード
}
.media {
    padding: 20px;
    border: 1px solid #900; //同じコード
    border-radius: 3px; //同じコード
    background: #fff; //同じコード
}
.article {
    background: #f7f7f7;
    border: 1px solid #900; //同じコード
    border-radius: 3px; //同じコード
    background: #fff; //同じコード
}

このように同じコードが含まれたコードが大量に出力されてしまいます。決して悪いとは言い切れないのですが、設計で解決できるのであればその方法を検討するとよいでしょう。

CSS設計で解決をする

たとえば先ほどのようなコードの場合は、テーマとしてスタイルを別に切り出してもよいかもしれません。

.theme-border{
    border: 1px solid #900; //同じコード
    border-radius: 3px; //同じコード
    background: #fff; //同じコード
}

.profile {
    width: 300px;
}

.media {
    padding: 20px;
}

.article {
    background: #f7f7f7;
}

使うときはこのようにします。

<div class="profile theme-border">
    <div class="profile-image"> ~ </div>
    <div class="profile-body"> ~ </div>
</div>

このようにクラスを複数つけることで解決ができます。またHTML上でスタイルの付与が完結する形になります。

私の場合

私の場合はまず@extendは使いません。理由は前述の通りで見通しがとても悪いと感じることが多かったからです。
またmixinは便利な機能でよく私も利用しています。ただ先ほどの例のように設計で解決できないかを考えた上でmixinを作ります。また複数人で運用するプロジェクトではどのようなmixinがあるかをコメントとしてちゃんと残すようにしています。

@importを使って設計に合わせたディレクトリ構成

smacss4
@importはCSSファイルを分割して管理できるためとても便利です。ただなんとなく分割をするのではなく、CSS設計に合わせた分割を心がけましょう。

SMACSSで教えるディレクトリ構成の考え方

style.scssというメインファイルに、他のファイルをすべて@importして一つのファイルとしてコンパイルする方法を提唱しています。

smacss5

style.scssの中身は以下のようになります。

@import 'setting';
@import 'mixin';

@import 'base';

@import 'layout/grid';
@import 'layout/alternate';

@import 'module/btn';
@import 'module/profile';
@import 'module/nav';

他のファイルは、ファイル名の先頭にアンダースコアをつけます。こうすることで、アンダースコアをつけたファイル単体でコンパイルされなくなります。

_setting.scss、_mixin.scssはファイル全体で使うファイルです。_setting.scssは主に変数、_mixin.scssにはmixinをまとめます。先頭で呼び出すことで、それ以下のファイルでも変数、mixinを使用できるようになります。

またSMACSSの場合はlayout、moduleというディレクトリを作成して、カテゴリごとにファイルを作ります。このときモジュールは1モジュール、1ファイルで作成します。

1モジュール、1ファイルで作成する理由

モジュールはさまざまな場所で使い回しができる前提で作られます。なので1つのファイルの中でスタイルが完結していることが望ましいです。
完結していない状態とは、たとえば以下のようなものを指します。

_card.scss
.card {
    float: left;
    padding: 15px;
    box-sizing: border-box;
}
.card-inner{
    background: color
}
_profile.scss
.profile {
    width: 300px;
}
.profile-image {
    > img {
        width: 100%;
        height: auto;
    }
}
.profile-body {
    background: white;
    font-size: 16px;
}
.card-inner 
    .profile{
        border: 1px solid #ccc;
    }
}

これはprofileモジュールの中でcardモジュールに依存した書き方をされています。このようなコードが増えていくとファイルを分けていてもモジュール同士の依存関係が生まれてしまい再利用しにくいモジュールになってしまいます。この場合はサブクラスにしたり、モジュールを分けたりすると良いでしょう。

このようなモジュールがある場合は、リファクタリングするように心がけることで、常にモジュール同士が依存しない保守性の高いコードを書くことができます。

私の場合

SMACSSではレイアウトルールとしてクラスの先頭に「l-」を付けるルールがあります。このような接頭語の考え方を_setting.scssでも取り入れるようにしています。

以下がその例です。

//color
$color-primary: #ff0000;
$color-secondary: #ffff00;
$color-link: #ffffff;

//width
$width-container: 1200px;
$width-main: 820px;
$width-sub: 360px;

//font-size
$font-size-xlarge: 28px;
$font-size-large: 20px;
$font-size-medium: 16px;
$font-size-small: 14px;
$font-size-xsmall: 12px;

このようにプロパティ名を接頭語としてつけておきます。このようにしておくとテキストエディタのサジェスト機能で探すのが楽になります。また新しく案件にアサインされた人でも、迷ったらとりあえずプロパティ名を打てばサジェストしてくれるので変数が把握しやすくなります。

smacss6

さいごに

いかがでしたでしょうか?
CSS設計は私も常に悩みながら設計をしています。そして設計しながらも途中で変更することはよく起こります。そこで気をつけてるのが、常にあとで変更しやすいコードを書いておくということ。
今回のお話が少しでも皆さんのCSS設計のお役に立てれば幸いです。そして今回の方法よりもより良い方法や、こうしたほうがもっと良いというご意見があればぜひ教えてください!

もっとよいコードが書きたいので、今後も頑張って突き詰めていきたいと思います! それでは。

店長
この記事を書いた人
店長

フロントエンドエンジニア

関連記事