現場で役立つ実践Sass(4)MixinとExtendの使いどころ

連載

現場で役立つ実践Sass

第4回目の今回は、Sassの利便性を大幅に加速させる、MixinとExtendの使いどころについて紹介します。

先日、生まれ変わったDreamweaverベータ版が遂に公開されました。Bracketsエンジンによるエディター機能の強化や、CSSプリプロセッサーに対応し、Dreamweaver内でSassのコンパイルも出来るようになりました。DreamweaverユーザーでまだSassを使っていない方は、この機会に試してみてはいかがでしょうか。
なお、Dreamweaverベータ版でのSassの使用方法については、「Dreamweaverで覚える最新Web開発ワークフロー: Sass編」が参考になります。

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

Mixinの使いどころ

Mixinの基本(引数は必須?)

最初に紹介するのは、引数のないプロパティを呼び出すだけのMixinの使用例です。

//コンパイル前(SCSS)
@mixin clearfix {
    &::after {
        content: "";
        display: block;
        clear: both;
    }
}

.card {
    @include clearfix;
}

//コンパイル後(CSS)
.card::after {
    content: "";
    display: block;
    clear: both;
}

Mixinは引数をうまく使うことでより便利になりますが、引数がないMixinを使っても何も問題はありません。
「引数がない場合、Mixinは使わない」という情報を目にしたことがあります。
しかし、引数が無いという理由でMixinの代わりにExtendを乱用し、大量のセレクタを書き出しているCSSの方が問題です。これは後ほどExtendの使いどころで説明します。

引数の初期値

では、引数を使ったmixin の使用例です。

//コンパイル前(SCSS)
@mixin heading($color:#333, $fz:18px) {
    color: $color;
    border-left: 2px solid $color;
    font-size: $fz;
}

.news-heading {
    @include heading;
}
.blog-heading {
    @include heading(green);
}
.about-heading {
    @include heading(red, 24px);
}


//コンパイル後(CSS)
.news-heading {
    color: #333;
    border-left: 2px solid #333;
    font-size: 18px;
}

.blog-heading {
    color: green;
    border-left: 2px solid green;
    font-size: 18px;
}

.about-heading {
    color: red;
    border-left: 2px solid red;
    font-size: 24px;
}

引数は コロン(:)で初期値がセットできます。
なるべく初期値をセットすることをオススメします。

初期値があれば、明示的な引数の指定がなくてもMixinは動作します。
また、引数の値を何度も書くことを防ぐのにも役に立ちます。
せっかくMixinなので、なるべく繰り返しの記述を省略しましょう。

複数の初期値の単一変更

Mixinで、複数の引数がある場合に、1つ目以降の引数を変更したい場合は、対象の変数を指定すれば、その箇所のみ引数指定が可能です。

//コンパイル前(SCSS)
.about-heading {
    @include heading($fz:24px);
}


//コンパイル後(CSS)
.about-heading {
    color: #333;
    border-left: 2px solid #333;
    font-size: 24px;
}<br>

この例では、2つ目の引数の$fzのみ変更しました。

可長変引数

引数の個数が変化する場合は可長変引数を使うと便利です。1つの可変長引数に対して、複数の値を渡すことできます。
特に、CSSグラデーションや複数背景指定など、値にカンマを使うプロパティでよく使います。

//コンパイル前(SCSS)
    @mixin v-gradient($color) {
        background-image: linear-gradient(to bottom, $color);
}

.box {
    @include v-gradient(#ff0084, #33001b);
}

//コンパイル後(エラー)
//Mixin v-gradient takes 1 argument but 2 were passed.

上のようなグラデーションのMixinでは、カンマ区切りで2個以上の値を指定するため、複数の引数として扱われ、上記のようにコンパイルエラーになります。

そこで、可長変引数として定義してみましょう。その場合、カンマ3つ(...)を引数の後に記述します。

//コンパイル前(SCSS)
@mixin v-gradient($color...) {
background-image: linear-gradient(to bottom, $color);
}

.box {
@include v-gradient(#ff0084, #33001b);
}
.box2 {
@include v-gradient(#ff0084, #33001b, #000);
}


//コンパイル後(CSS)
.box {
background-image: linear-gradient(to bottom, #ff0084, #33001b);
}

.box2 {
background-image: linear-gradient(to bottom, #ff0084, #33001b, #000);
}

これで引数の値が何個になっても$colorと置き換えられます。
なお、可能変引数には初期値を設定できません。

@content と 次期At-Rules

本連載第2回「メディアクエリを mixin でまとめる」の章で@contentを使ったメディアクエリのMixinを紹介しました。
メディアクエリと同様に、他の At-Rules にもMixinと@contentを使うと便利です。

@supports

ブラウザのプロパティのサポート状況で条件分岐ができる @supports は今後よく使われそうなAt-Rulesです。
Mixinにまとめて、より省略して記述することもできます。

$flex: "display:flex";

@mixin unsupports($property) {
    $property: "(#{$property})"; //これがないと丸括弧がなくなってしまうので二重で囲う
    @supports not (#{$property}) {
        @content;
    }
}

.card {
    display: flex;
    @include unsupports($flex) {
        display: block;
    }
}

display:flex をサポートしていないブラウザで読み込みされるとMixinでプロパティを与えるようにしています。
Mixinでは @suppots not をつかい使用して、プロパティをサポートしない場合に対応しています。

上記の例のように1つのプロパティで @supports を使う場合は引数無しのMixinに書き換えてもよさそうです。

//コンパイル前(SCSS)
@mixin not-flex {
    @supports not (display:flex) {
        @content;
    }
}

.card2 {
    display: flex;
    @include not-flex {
        display: block;
    }
}


//コンパイル後(CSS)
.card {
    display: flex;
}
@supports not (display:flex) {
    .card {
        display: block;
    }
}

@document

CSS4に延期になってしまったので、まだ使うことはできませんが、URLなどサイト別に条件分岐ができる @document というルールがあります。
こちらもSassでネストして書けます。

//コンパイル前(SCSS)
body {
    color: #000;
    @document url(https://blogs.adobe.com/) {
        color: #444;
    }
}

//コンパイル後(CSS)
body {
    color: #000;
}
@document url(https://blogs.adobe.com/) {
    body {
        color: #444;
    }
}

blogs.adobe.com のURLのみ文字色を変える指定をしています。

@document もMixinを使えば、より汎用的な書き方ができそうです。

$acs: "https://blogs.adobe.com/creativestation/";

@mixin url($url){
    @document url(#{$url}) {
    @content;
    }
}

body {
    color: #000;
    @include url($acs){
        color: #444;
    }
}

ブラウザ実装が楽しみです。

CSS @apply Rule

CSSでもネイティブでMixinのような機能を持てる CSS @apply Rule というアイデアがあります。
この仕様については、実際に採用されるかまだ分かりませんが、コンパイルせずCSSで出来るようになれば確実に便利になります。
ブラウザの実装に期待したいです。

https://tabatkins.github.io/specs/css-apply-rule/

Extendの使いどころ

Extendの使い方を見直そう

スタイルを継承するExtend。大変便利で強力な機能でありますが、誤った使い方をすると思わぬ副作用が出てきます。
本当にExtendである必要があるのかをよく考えましょう。

ExtendではなくMixinの方が適した事例として代表的なものは、clearfixのPlaceHolderセレクタです。

// コンパイル前(SCSS)
%clearfix {
    &::after {
        content: "";
        display: block;
        clear: both;
    }
}

よく見る使用例かもしれませんが、どこからでも継承する目的ではExtendを使うべきではありません。
このExtendをサイト全体で使ってみたところ、以下の様なCSSが書き出されました。

// コンパイル後(CSS)
.wrapper::after, .header::after, .nav::after, .box::after, .wrap::after, .card::after, .card .card__item::after, .dl__block::after, .dl__block dt::after,
.dl__block dd::after, .list::after, .list > li::after, .toolbar::after  {
    content: "";
    display: block;
    clear: both;
}

大量のセレクタがグルーピングされ、cleafixが指定されました。
実際にはこれの数倍、もしくは数十倍のセレクタがグルーピングされることも往々にあります。
そのため、書き出されるソースの見通しが非常に悪くなります。

しかし、それよりも問題なのは、グローバルに継承したExtendは、全てのモジュールのセレクタを一つにまとめてしまうことです。これによりコンパイル後は、記述される順番が変わってしまいます。

下のコードを見てください。このモジュールではマージンの相殺を行いたくないので、cleafixを display: table; で上書き指定しようとしています。

.box {
    @extend %clearfix
    &::after {
        display: table;
    }
}

しかし、もしも継承したいセレクタがこのルールセットより下に書かれていた場合、このようにコンパイルされます。

.box::after {
    display: table;
}
.box::after {
    content: "";
    display: block;
    clear: both;
}

CSSは同じ詳細度の場合、後に書かれているスタイルで上書きされます。つまり display: table; は適用されません。
このように、Extendは、継承元のセレクタと継承先のセレクタが一緒に書かれてしまうので、Sassと記述の順番が変わり優先度が変更される場合があります。
気づかないうちにスタイル適用のルールが変わってしまい、レイアウトが崩れる可能性も考えられます。

それ以外にも、IE9以下は1ファイルのCSSのセレクタが4,095個を超えるとそれ以降のスタイルが適用されない仕様がありますが、Extendは多用することでセレクタが倍増するので、4,095個を超える可能性も懸念されます。

ですので、clearfixのようなグローバルで使うスタイルに関しては、前半のMixinの章で紹介した、このようなコードを使うべきと考えます。

@mixin clearfix {
    &::after {
        content: "";
        display: block;
        clear: both;
    }
}

加えて引数を使えばマージンの相殺の有無も管理できます。

@mixin clearfix($display: block) {
    &::after {
        content: "";
        display: $display;
        clear: both;
    }
}

Extendを使うべきパターン

Extendを本来の使うべきパターンは下記のようなものと考えます。
単純に重複したスタイルをまとめるためではなく、同じコンポーネント内のみで使うなど、スコープを決めて使いましょう。

%card {
    width: 33%;
    padding: 1em;
    border: 2px solid #333;
    border-radius: 5px;
}

.card {
    @extend %card;
    background-color: #fff;
}
.card--primary {
    @extend %card;
    border-color: #ccc;
    background-color: #00f;
    color: #fff;
}
.card--secondary {
    @extend %card;
    border-color: #000;
    background-color: #000;
    color: #fff;
}
.card--caution {
    @extend %card;
    border-color: #f33;
    background-color: #ff3;
}

Extendが使えると重複したスタイルを全てまとめたい衝動にかられますが、コンパイル後のCSSが想像できる範囲で使うべきでしょう。
後々のメンテナンス性などを考え、Mixinとうまく使い分けてください。