Sass(Scss) : メディアクエリ用のミックスイン

、『Sass入門 〜より効率的なCSSコーディング』(※電子書籍のみです)にSass 3.2の内容を追加したので、記念にメディアクエリ用のミックスインを作りました。

@mediaルールを都度記述するのは面倒ですし、同じ要素やモジュールのスタイルを各@mediaルールに分けて書くことも面倒です。今回作ったミックスインを使えば、これらの問題を解決することができます。

出力したいCSSの例

例えば次のようなCSSを出力したい場合があるとします。

CSS
/* 全てに適用 */
h1 { width: 100%; }
p { line-height: 1.4; }

/* 主にPC用 */
@media only screen and (min-width: 801px) {
  h1 { color: red; }
  p { margin: 1em 0; }
}

/* タブレット用 */
@media only screen and (min-width: 481px) and (max-width: 800px) {
  h1 { color: blue; }
  p { margin: 0.8em 0; }
}

/* スマートフォン用 */
@media only screen and (max-width: 480px) {
  h1 { color: green; }
  p { margin: 0.6em 0; }
}

これを2種類の方法で書くことができます。

  1. 要素やモジュール単位で書く方法
  2. メディア特性ごとにスタイルを書く方法

両方とも@include mq {}の中にスタイルを書き、@mediaの代わりに@ifで分けます。

1. 要素やモジュール単位でスタイルを書く方法

Scss
@include mq {
    h1 {
        @if $mq-all { // 全てに適用
            width: 100%;
        }
        @if $mq-default { // 主にPC用
            color: red;
        }
        @if $mq-tablet { // タブレット用
            color: blue;
        }
        @if $mq-sp { // スマートフォン用
            color: green;
        }
    }
    p {
        @if $mq-all {
            line-height: 1.4;
        }
        @if $mq-default {
            margin: 1em 0;
        }
        @if $mq-tablet {
            margin: 0.8em 0;
        }
        @if $mq-sp {
            margin: 0.6em 0;
        }
    }
}

2. メディア特性ごとにスタイルを書く方法

Scss
@include mq {
    @if $mq-all { // 全てに適用
        h1 {...}
        p {...}
    }
    @if $mq-default { // 主にPC用
        h1 {...}
        p {...}
    }
    @if $mq-tablet { // タブレット用
        h1 {...}
        p {...}
    }
    @if $mq-sp { // スマートフォン用
        h1 {...}
        p {...}
    }
}

1つの要素やモジュールのスタイルを一箇所に記述できるので、どちらかと言えば1つ目の方法が好みですが、場合によっては2つ目の方法も使うと良いと思います。

実は、この2つの方法を一緒に使うこともできます。

2つの方法を一緒に使う

例えば、スマートフォン向けにスタイルの追加や上書きをまとめて書きたい場合は@if $mq-sp {}の中にまとめてスタイルを書いたほうがラクです。

次のコードではh1は1つ目の方法で、それ以外は2つ目の方法で書いています。

Scss
@include mq {
    h1 {
        @if $mq-tablet {
            color: blue;
        }
        @if $mq-sp {
            color: green;
        }
    }
    @if $mq-sp {
        header { background: url(...); }
        p { margin: 0.6em 0; }
        nav { width: 100%; }
            :
    }
}

これをコンパイルすると次のようになります。

CSS
@media only screen and (min-width: 481px) and (max-width: 800px) {
  h1 {
    color: blue;
  }
}
@media only screen and (max-width: 480px) {
  h1 {
    color: green;
  }

  header {
    background: url(...);
  }

  p {
    margin: 0.6em 0;
  }

  nav {
    width: 100%;
  }
    :
}

h1の中で書いたスマートフォン用のスタイルも同じ@mediaルール内に出力されています。

このように、スタイルを書く場所が仮にバラバラであっても、メディア特性ごとの@mediaルール内にまとめられるのもこのミックスインの特長です。無駄な@mediaルールが出力されないのでCSSのファイルサイズを減らすことができます。

スタイルの出力を変数で制御しているので、@if文で2つ以上の変数を利用することで次のようなことができるのもこのミックスインの特長です。

2つの任意のメディア特性に同じスタイルを適用したい場合

同じコードを2箇所以上に書くのは避けたいところですが、タブレットとスマートフォンに同じスタイルを適用したいといった場合は次のようにします。

Scss
@include mq {
    h1 {
        @if $mq-tablet {
            margin: 0;
        }
        // タブレット "と" スマートフォンですが、
        // and ではなく or を使います
        @if $mq-tablet or $mq-sp {
            color: blue;
        }
    }
}

これをコンパイルすると次のようになります。

CSS
@media only screen and (min-width: 481px) and (max-width: 800px) {
  h1 {
    margin: 0;
    color: blue;
  }
}
@media only screen and (max-width: 480px) {
  h1 {
    color: blue;
  }
}

color: blue;はタブレット用とスマートフォン用の@mediaルール内に出力されています。当然、タブレット用のh1のスタイルはまとめられています。

メディア特性ごとにCSSファイル自体を分ける場合

メディア特性ごとにCSSファイル自体を分けてlink要素でそれぞれのファイルを読み込ませたい場合は次のようにします。

まず、スタイルを記述するパーシャルファイルを用意します(ここでは_mq.scssとします)。スタイルは要素やモジュール単位でも良いですし、メディア特性ごとに書いても良いです。

Scss
[_mq.scss]
h1 {
    @if $mq-all {
        width: 100%;
    }
    @if $mq-default {
        color: red;
    }
    @if $mq-tablet {
        color: blue;
    }
    @if $mq-sp {
        color: green;
    }
}
p {
    @if $mq-all {
        line-height: 1.4;
    }
    @if $mq-default {
        margin: 1em 0;
    }
    @if $mq-tablet {
        margin: 0.8em 0;
    }
    @if $mq-sp {
        margin: 0.6em 0;
    }
}

これを次のように各ファイルで読み込ませます。

Scss
[default.scss]
$mq-all: true;
$mq-default: true;
@import "mq";

[tablet.scss]
$mq-all: true;
$mq-tablet true;
@import "mq";

[sp.scss]
$mq-all: true;
$mq-sp true;
@import "mq";

コンパイル後のdefault.cssは次のようになります。

CSS
h1 {
  width: 100%;
  color: red;
}
p {
  line-height: 1.4;
  margin: 1em 0;
}

複数ファイルを@importしても問題ないので、スタイルをカテゴリごとや作業者ごとなど、別々のファイルに分けて管理することもできます。

ミックスインと設定用の変数

最後にそのミックスインと設定用の変数についてです。

ミックスインmqでは実は4つのミックスインを@includeして、それぞれに@contentでスタイルを渡しているだけです(@contentについては『Sass入門』を読んでもらうとして...)。とりあえず次に挙げるものが全てのコードになります。

Scss
// # Media Queries
//
// ## Breakpoints (Window Size)
//
// 0-480   : [Smartphone]
//           iPhone3/4/5(Portrait), iPhone3/4(Landscape)
//           Most Android Phone(Portrait)
// 481-800 : [Tablet, Smartphone(Landscape)]
//           iPhone5(Landscape), iPad(Portrait)
//           Most Android Phone(Landscape)
//           Newer Android Tablet(Portrait)
// 801+    : Default, iPad(Landscape), Newer Android Tablet(Landscape)
//  OR 801-1024
// 1025+   : Large Window Size, Newer Android Tablet(Landscape)

// ## Variables
$mq-all    : null !default;
$mq-sp     : null !default;
$mq-tablet : null !default;
$mq-default: null !default;
$mq-large  : null !default;

$default-mq-sp-max-width     : 480px !default;
$default-mq-tablet-min-width : $default-mq-sp-max-width + 1 !default;
$default-mq-tablet-max-width : 800px !default;
$default-mq-default-min-width: $default-mq-tablet-max-width + 1 !default;
$default-mq-large-min-width  : 1025px !default;
$default-mq-default-max-width: null !default;
// $default-mq-default-max-width: $default-mq-large-min-width - 1 !default;

// ## Wrapper
@mixin mq {
    @include mq-all     { @content; }
    // @include mq-large   { @content; }
    @include mq-default { @content; }
    @include mq-tablet  { @content; }
    @include mq-sp      { @content; }
}

// ## For All
@mixin mq-all {
    $_tmp: $mq-all;
    $mq-all: true;
    @content;
    $mq-all: $_tmp;
}

// ## For Smartphones
@mixin mq-sp(
    $max: $default-mq-sp-max-width
) {
    $_tmp: $mq-sp;
    $mq-sp: true;
    @media only screen and (max-width:#{$max}) {
        @content;
    }
    $mq-sp: $_tmp;
}

// ## For Tablets
@mixin mq-tablet(
    $min: $default-mq-tablet-min-width,
    $max: $default-mq-tablet-max-width
) {
    $_tmp: $mq-tablet;
    $mq-tablet: true;
    @media only screen and (min-width:#{$min}) and (max-width:#{$max}) {
        @content;
    }
    $mq-tablet: $_tmp;
}

// ## For PC (default)
@mixin mq-default(
    $min: $default-mq-default-min-width,
    $max: $default-mq-default-max-width
) {
    $_tmp: $mq-default;
    $mq-default: true;
    $_breakpoint: "(min-width:#{$min}) ";
    @if not($max == null) {
        $_breakpoint: $_breakpoint + "and (max-width:#{$max}) ";
    }
    @media only screen and #{$_breakpoint}{
        @content;
    }
    $mq-default: $_tmp;
}

// ## For Large Window
@mixin mq-large(
    $min: $default-mq-large-min-width
) {
    $_tmp: $mq-large;
    $mq-large: true;
    @media only screen and (min-width:#{$min}) {
        @content;
    }
    $mq-large: $_tmp;
}

ブレイクポイントについては制作するサイトの仕様に合わせて設定用の変数の値を適宜変更します。また、これまでのサンプルでは触れていませんが、大きめのウィンドウサイズ用のミックスインと変数のように必要に応じて別のものを新たに追加することもできるようになっています。

子のミックスイン

ミックスインmq@includeされている4つのミックスインについてですが、行なっていることはほとんど同じなのでmq-tabletを説明することにします。

Scss
// ## Variables
$mq-tablet : null !default;

$default-mq-sp-max-width     : 480px !default;
$default-mq-tablet-min-width : $default-mq-sp-max-width + 1 !default;
$default-mq-tablet-max-width : 800px !default;

// ## For Tablets
@mixin mq-tablet(
    $min: $default-mq-tablet-min-width,
    $max: $default-mq-tablet-max-width
) {
    $_tmp: $mq-tablet;
    $mq-tablet: true;
    @media only screen and (min-width:#{$min}) and (max-width:#{$max}) {
        @content;
    }
    $mq-tablet: $_tmp;
}

ミックスインmq-tabletでは変数$mq-tabletの値を一時的に$_tmpに保存しておいてからtrueにしています。そのあと、@mediaルールの中に@content;があるのでここにスタイルが展開されますが、その時に@if $mq-tablet {}の中のスタイルだけが出力されます。そして、最後に保存しておいた$mq-tabletの値を戻しています(外で設定された値を変えないようにしています)。

実は、子のミックスインは単体でも使うことができます。@mediaルールが@includeする度に出力されてしまいますが、局所的に使う場合には@mediaルールを書くよりもラクなので便利だと思います。

Scss
h1 {
    width: 100%;
      :
    @include mq-tablet {
        color: blue;
    }
}
コンパイル後のCSSは次のようになります。
CSS
h1 {
  width: 100%;
    :
}
@media only screen and (min-width: 481px) and (max-width: 800px) {
  h1 {
    color: blue;
  }
}

以上ですが、それにしてもメディアクエリって面倒ですね...。