現場で役立つ実践Sass(2)セレクタをもっと便利に書く

連載

現場で役立つ実践Sass

Sassの便利な機能としてまず最初に挙げるとすれば、セレクタをネストして記述できることでしょう。
ネストできることにより、CSSでは繰り返し書く必要があった子孫セレクタをまとめて書くことができ、記述量が減って、コードの見通しも良くなります。ネストができないCSSにはもう戻れないという方も多い…はず。

第二回目の今回は、セレクタ周りのTipsを紹介します。

※今記事はRuby Sass 3.4.21で検証して書かれています。LibSassや、Sassのバージョンが違う場合は、コンパイル結果が異なる場合があります。

1. &(アンパサンド)

親セレクタを参照する &(アンパサンド)。
擬似クラス・擬似要素や属性セレクタでよく使います。下の例ではコンパイル後には & が親要素の a を参照します。

// コンパイル前(SCSS)
a {
    color: red;
    &.current {
        color: blue;
    }
    &:hover {
        text-decoration: underline;
    }
}

// コンパイル後(CSS)
a {
    color: red;
}
a.current {
    color: blue;
}
a:hover {
    text-decoration: underline;
}

& を後部に書くこともできます。
body要素にクラスを付けてスタイルを変える場合などに使えます。

// コンパイル前(SCSS)
.box {
    color: red;
    .bodyClass & {
         color: blue
    }
}

// コンパイル後(CSS)
.box {
    color: red;
}
.bodyClass .box {
    color: blue;
}

特定のマージンなどを、オーバーライドする場合にもよく使います。

// コンパイル前(SCSS)
h1 {
    margin-top: 40px
    &:first-child {
        margin-top: 0px
    }
}

上の例は IE8 以下のサポートが終了した現在では、下のように :not を使って書いてもいいでしょう。

// コンパイル前(SCSS)
h1 {
    &:not(:first-child) {
        margin-top: 40px
    }
}

& をトリガーにしたスニペットを作っておく

&:not(:first-child) なんて長くて毎回書くのは面倒ですよね。
擬似セレクタは「&」をトリガーにしたスニペットを作っておくと便利です。
そこで第一回で紹介した Bracketsの拡張「Brackets Snippets (by edc) 」にスニペットを登録してみました。
& を入力するだけでコードヒントに表示されるようになります。

Bracketsに「&」のスニペット登録
https://blogs.adobe.com/creativestation/files/2016/01/BracketsScreenShot-002.png

「&」 を入力すると擬似セレクタが保管される

「&」をトリガーにしたスニペットを Export してアップロードしました。よろしければ試してみてください。
ファイルを解凍し、Bracketsの右アイコンメニューから、Brackets Snippets (by edc)のパネルを開き、Setting > Import からインポートできます。

BEMを & で書く

BEM とは梱包する要素「Block」 に、子要素の「Element(__) 」、状態をあらわす「Modifier(–)」 を繋げてクラス名を書いてゆく命名規則のことです。

.block {}
.block__element {}
.block__element--modifier {}

※ 厳密には、BEM派生の MindBEMding を使います。詳細はリンクをご確認ください。

Sassでは & を使うことで BEM を便利に書くことができます。

// コンパイル前(SCSS)
.block {
    display: block;
    &__element {
        display: inline-block;
        &--modifier {
            background-color: #f00;
        }
    }
}

// コンパイル後(CSS)
.block {
    display: block;
}
.block__element {
    display: inline-block;
}
.block__element--modifier {
    background-color: #f00;
}

このように & で繋げたセレクターは、シングルクラスとしてコンパイルされます。

Block に梱包して、名前空間を確保したい場合は Element を & &__ のように、Elementの手前に & を一つ書き加え、2回参照することもできます。

// コンパイル前(SCSS)
.block {
    display: block;
    & &__element {
        display: inline-block;
        &--modifier {
            background-color: #f00;
        }
    }
}
// コンパイル後(CSS)
.block {
    display: block;
}
.block .block__element {
    display: inline-block;
}
.block .block__element--modifier {
    background-color: #f00;
}

Block の中に Element がネストしてコンパイルされました。

このBEMを & で書く記述方法は、速く、楽にBEMを書くことは出来るのですが、見慣れていないと可読性が落ちます。
Sassファイルを Element のクラス名 .block__element などと検索してもヒットしないというデメリットもあります。
チームで使う場合は、ルールを決めておくことをオススメします。

2. プロパティのネスト

プロパティもネストすることができます。
下の例では font で始まるプロパティをまとめて指定しています。

// コンパイル前(SCSS)
.text {
    font: {
        family: source-sans-pro, sans-serif;
        size: 1.2rem;
        weight: normal;
    }
}

// コンパイル後(CSS)
.text {
    font-family: source-sans-pro, sans-serif;
    font-size: 1.2rem;
    font-weight: normal;
}

通常のコーディングではあまり使うことはありませんが、mixin などで使うことがあります。
できるということだけ憶えておきましょう。ハイフンを使うプロパティであれば使えます。

3. メディアクエリのネスト

Sassでは、メディアクエリも、ネストして書くことができます。
下の例を見てみましょう。

// コンパイル前(SCSS)
h1 {
    font-size: 2em;
    @media screen and (min-width: 768px) {
        font-size: 3rem;
    }
}

// コンパイル後(CSS)
h1 {
    font-size: 2em;
}
@media screen and (min-width: 768px) {
    h1 {
        font-size: 3rem;
    }
}

h1 に対するフォントサイズの指定を一箇所にまとめて記述しています。
メディアクエリをCSSの下部や別ファイルに書いて、再度同じセレクタを指定してゆく…という工程を省略することができます。

さらに、重ねてネストすることで、複数の条件に対する指定をまとめることができます。

// コンパイル前(SCSS)
h1 {
    font-size: 2em;
    @media screen and (min-width: 768px) {
        font-size: 3rem;
        @media screen and (max-width: 1200px) {
            font-size: 4rem;
        }
    }
}

// コンパイル後(CSS)
h1 {
    font-size: 2em;
}
@media screen and (min-width: 768px) {
    h1 {
        font-size: 3rem;
    }
}
@media screen and (min-width: 768px) and (max-width: 1200px) {
    h1 {
        font-size: 4rem;
    }
}

ここではメディアクエリを紹介しましたが、@media print や、@supports などの CSS Conditional Rules は全て同様に記述することができます。

メディアクエリを mixin でまとめる

セレクタごとに @media screen and (min-width: 768px) といった指定を記述するのは面倒です。このような場合は、mixinでまとめると楽になります。

// コンパイル前(SCSS)
@mixin mq-tablet {
    @media screen and (min-width: 768px) {
        @content;
    }
}
h2 {
    font-size: 1.8em;
    @include mq-tablet {
        font-size: 2.4rem;
    }
}
h3 {
    font-size: 1.6em;
    @include mq-tablet {
        font-size: 2rem;
    }
}

// コンパイル後(CSS)
h2 {
    font-size: 1.8em;
}
@media screen and (min-width: 768px) {
    h2 {
        font-size: 2.4rem;
    }
}
h3 {
    font-size: 1.6em;
}
@media screen and (min-width: 768px) {
    h3 {
        font-size: 2rem;
    }
}

上の例ではタブレット用のメディアクエリをミックスイン mq-tablet にしています。
mixinにしておけば、ブレイクポイントに変更があった場合なども柔軟に対応できます。

メディアクエリをネストしたときの問題点

コンパイル後のCSSを見ると、同じメディアクエリの指定が複数個所に記述されています。できれば一箇所にまとめたいところです。
これはSassだけではどうにもできませんが、第一回で紹介したポストプロセッサーを使い、コンパイル後のCSSを処理すれば解決できます。
Sassのコンパイル後に、自動で実行してくれるタスクランナーを使うと楽でしょう。Gulpを使うなら、バラバラになったメディアクエリをまとめる Gulpプラグインが何個か見つかりました。

ポストプロセス処理を実行したCSSを見てみましょう。

h2 {
    font-size: 1.8em;
}
h3 {
    font-size: 1.6em;
}

@media screen and (min-width: 768px) {
    h2 {
        font-size: 2.4rem;
    }
    h3 {
        font-size: 2rem;
    }
}

メディアクエリによる設定をまとめてくれました。

Gulpを使わなくても group-css-media-queries を npm でインストールすれば、コマンドラインツールからまとめることもできます。
ただしこの場合は1回のみの処理となります。

group-css-media-queries input.css output.css

4. @at-root ディレクティブ

@at-root を指定して、ネストの中から対象のセレクタをルートに戻すことができます。

// コンパイル前(SCSS)
.block {
    display: block;
    & &__element {
        display: inline-block;
        &--modifier {
            background-color: #f00;
            @at-root .root {
                color: #c00;
            }
        }
    }
}

// コンパイル後(CSS)
.block {
    display: block;
}
.block .block__element {
    display: inline-block;
}
.block .block__element--modifier {
    background-color: #f00;
}
.root {
    color: #c00;
}

ネストの中から .root がルートに戻っているのがわかります。ネストが深い場合にルートに書き出せます。
ただ、このくらいのネストの深さで、Sassを記述する段階でルートを分けて書いてもいいでしょう。

メディアクエリ内の @at-root

@at-root にはオプションがあり、メディアクエリの中でセレクタの書き出しをコントロールすることがなどができます。

まずは、オプション無しで、メディアクエリ内に @at-root を指定してみます。

// コンパイル前(SCSS)
.wrap {
    width: 50%;
    @media (min-width: 480px) { 
        width: 100%;
        @at-root { 
            .box { 
                display: block; 
            }
        } 
    }
}

// コンパイル後(CSS)
.wrap {
  width: 50%;
}
@media (min-width: 480px) {
  .wrap {
    width: 100%;
  }
  .box {
    display: block;
  }
}

コンパイル後の .box の指定は @media のブロックの外には出ませんが .wrap との入れ子関係は無い常態になることがわかります。

@at-root (without: media)

今度は @at-root にオプション (without: media) を指定します。

// コンパイル前(SCSS)
.wrap {
    width: 50%;
    @media (min-width: 480px) { 
        width: 100%;
        @at-root (without: media) { 
            .box { 
                display: block; 
            }
        } 
    }
}

// コンパイル後(CSS)
.wrap {
  width: 50%;
}
@media (min-width: 480px) {
  .wrap {
    width: 100%;
  }
}
.wrap .box {
    display: block;
}

.box の指定が @media ブロックの外に出ました。
.wrap のネストには入ったままです。without: media を指定すると @media による指定が無視されます。

今はメディアクエリの中なので without: media を指定していますが、@supports などでも without: supports のように指定できます。

@at-root (without: media rule)

次に、rule も追加して without: media rule と指定してみましょう。

// コンパイル前(SCSS)
.wrap {
    width: 50%;
    @media (min-width: 480px) { 
        width: 100%;
        @at-root (without: media rule) { 
            .box { 
                display: block; 
            }
        } 
    }
}

// コンパイル後(CSS)
.wrap {
  width: 50%;
}
@media (min-width: 480px) {
  .wrap {
    width: 100%;
  }
}
.box {
    display: block;
}

.box@media からも .wrap のネストからも外れてルートに書き出されました。

これはmediaとルールセットがどちらも除外されたためです。
@at-root (without: all) という全てを除外するオプションを指定しても、同じことができます。

逆に with という除外しないためのオプション指定もありますが、基本的には without を多く使うことになるでしょう。

5. セレクタを置換する

Sassには便利な関数が予め用意されています。
セレクタ用の関数では、セレクタをネストしたり、結合したり、親要素を参照したりできます。

Selector Functions — Sass Documentation

今回は、その中でも便利な、セレクタを置換する selector-replace関数を紹介します。

selector-replace($selector, $original, $replacement)

第一引数に置換範囲を示すセレクタ、第二引数に置換前のセレクタ、第三引数に置換後のセレクタを指定します。
通常、第一引数は & を指定すればいいので、第二、第三引数に必要な文字列(セレクタ)を指定します。結果は文字列として返ってくるので、それを @at-root に続けて、インターポレーション(#{ })で囲います。

// コンパイル前(SCSS)
.box {
    .text {
        a {
            text-decoration: underline;
            @at-root #{selector-replace(&, ".text", ".card")} {
                color: orange;
                text-decoration: none;
            }
        }
    }
}

// コンパイル後(CSS)
.box .text a {
    text-decoration: underline;
}
.box .card a {
    color: orange;
    text-decoration: none;
}

.text.card に置換されているのがわかります。
selector-replace関数を使って親セレクタの置換もできるので、ネストの深い位置から途中のセレクタを変更したスタイルを指定することができます。

上の例のような置換はよく使うので、これも mixin にすると便利でしょう。

// コンパイル前(SCSS)
@mixin replace ($original, $replacement) {
    @at-root #{selector-replace(&, $original, $replacement)} {
        @content
    }
}
.box {
    .text {
        a {
            text-decoration: underline;
            @include replace(".text", ".card") {
                color: orange;
                text-decoration: none;
            }
        }
    }
}

// コンパイル後(CSS)
.box .text a {
    text-decoration: underline;
}
.box .card a {
    color: orange;
    text-decoration: none;
}

置換前後のセレクタを書くだけのシンプルな mixin になりました。

Sassのネストや関数を活用すれば、CSSでは冗長になってしまうセレクタの指定を、効率よく記述できます。

セレクタを効率よく書くことができれば、コーディングのスピートも上がるでしょう。また、可読性が上がることで、コードの修正や拡張もしやすくなることでしょう。