Sassで作るレスポンシブに対応したVertical Rhythm

Vertical Rhythm（バーティカルリズム）とは要素の配置に一定のリズム（パターン）を取り入れた手法のひとつです。具体的には行の高さ（CSSでは`line-height`の値）を1つの単位として、要素間の余白を決めます。ノートの罫線をイメージすると分かりやすいと思います。

[Gridlover](http://www.gridlover.net/app/)のようなサービスを使うと簡単に作成することもできますが、レスポンシブには対応していません。今回はSassを使用してレスポンシブに対応したVertical Rhythmを生成したいと思います。デモはCodePenで確認できます。

<p data-height="266" data-theme-id="4660" data-slug-hash="WQRYJy" data-default-tab="result" data-user="gaku" class='codepen'>See the Pen <a href='http://codepen.io/gaku/pen/WQRYJy/'>Responsive Vertical Rhythm</a> by ManabuYasuda (<a href='http://codepen.io/gaku'>@gaku</a>) on <a href='http://codepen.io'>CodePen</a>.</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>

## ブレイクポイントを定義したマップ
マップはkeyとvalueのセットを定義できるSassのデータ型のひとつです。ここにブレイクポイントを定義します。

```scss
// ブレイクポイントを管理するためのmapです。
// ----------------------------
// @type Map
// @prop {String} keys - ブレイクポイントのキーワードです。
// @prop {String} values - メディアクエリの条件です。
$breakpoints: (
    'sm': 'screen and (min-width: 400px)',
    'md': 'screen and (min-width: 768px)',
    'lg': 'screen and (min-width: 1000px)',
    'xl': 'screen and (min-width: 1200px)',
) !default;
```

## フォントサイズを定義したマップと@function
フォントサイズもマップで定義をしますが、@mixinを複雑にしないために@functionで必要な値（フォントサイズ、行の高さの絶対値、行の高さの相対値）がリスト型で受け取れるようにします。

```scss
// pxの引数を渡して単位なしとremの相対値を返す関数です。
// ----------------------------------------------
// @type Funciton
// @prop {Number} $fs - `font-size`に指定する値をpxで指定します。
// @prop {Number} $lh - `line-height`に指定する値をpxで指定します。
// @return {List} - 第一引数, 第二引数, 第二引数と第一引数の除算の単位なしの相対値, 第二引数と第一引数の除算のremの相対値
// @require {Map} $font-sizes - フォントサイズと行の高さを定義しているマップです。
//
// @example scss - Usage
// vr(14px, 24px) => 14px, 24px, 1.715, 1.715rem
//
@function vr($fs, $lh) {
    // 小数点以下を3桁までに制限します。100に変更すると2桁に制限できます。
    $n: 1000;
    $vr: ceil(($lh / $fs) * $n) / $n;
    $rem: $vr * 1rem;
    @return ($fs, $lh, $vr, $rem);
}
```

フォントサイズの指定はこのようにします。

```scss
// フォントサイズと行の高さを定義しているマップです。
// keyには"base"と$breakpointsで定義したkeyを定義します。
// valueにはフォントサイズと行の高さをpxで定義します。
// --------------------------------------------------------
// $base-font-sizesの第二引数の値の倍数を各mapに定義します。
// @prop {String} keys - keyは呼び出す時のキーワードになります。
// @prop {String} values - 呼び出される`font-size`と`line-height`になります。
// @requires {Function} vr - pxの引数を渡して単位なしとremの相対値を返す関数です。
// @see {Map} $breakpoints - ブレイクポイントを管理するmapです。同じkeyを指定してくだい。
$base-font-sizes: (
    'base': vr(14px, 24px),
    'lg': vr(16px, 28px),
) !default;

$h1-font-sizes: (
    'base': vr(26px, 24px * 2),
    'lg': vr(30px, 28px * 2),
) !default;

$h2-font-sizes: (
    'base': vr(22px, 24px),
    'lg': vr(26px, 28px),
) !default;

$h3-font-sizes: (
    'base': vr(20px, 24px),
    'lg': vr(24px, 28px),
) !default;

$h4-font-sizes: (
    'base': vr(16px, 24px),
    'lg': vr(20px, 28px),
) !default;

$h5-font-sizes: (
    'base': vr(14px, 24px),
    'lg': vr(16px, 28px),
) !default;

$h6-font-sizes: (
    'base': vr(12px, 24px),
    'lg': vr(14px, 28px),
) !default;
```

`'base'`は0pxから適応されるフォントサイズになります。それ以外は`'sm'`や`'md'`のようにブレイクポイントを定義したマップのkeyと同じになるようにします。

valueには先ほど作成した`vr()`関数を使って、フォントサイズと行の高さをpxの単位をつけて指定します。同じkeyには同じ行の高さが指定されるようにします。

## @mixin
最後に`font-size`と`line-height`、`margin`などの余白を指定する@mixinを作成します。

フォントサイズと行の高さを指定する@mixinです。

```scss
// Vertical-rhythmに対応した`font-size`(px)と`line-height`(単位なし)を返すmixinです。
// ------------------------------------------------------------------------
// @type Mixin
// @prop {Map} $fs($base-font-sizes) -
// @requires {Map} $base-font-sizes - rootのフォントサイズと行の高さを定義しているマップです。
// @requires {Map} $breakpoints - ブレイクポイントを管理するためのmapです。
//
// @example scss - Usage
// .foo {
//     @include font-size($base-font-sizes);
// }
//
// @example css - CSS output
// .foo {
//     font-size: 14px;
//     line-height: 1.715;
// }
//
// @media screen and (min-width: 1000px) {
//     .foo {
//         font-size: 16px;
//         line-height: 1.75;
//     }
// }
//
@mixin font-size($fs: $base-font-sizes) {
    $bp: $breakpoints;
    @each $name, $unit in $fs {
        @if $name == "base" {
             font-size: nth($unit, 1);
             line-height: nth($unit, 3);
        } @else {
            @if map-has-key($bp, $name) {
                @media #{map-get($bp, $name)} {
                    font-size: nth($unit, 1);
                    line-height: nth($unit, 3);
                }
            }
        }
    }
}
```

@eachでマップのkeyとvalueを取り出します。フォントサイズ用のマップのkeyが`'base'`の時にはブレイクポイントを入れずに、それ以外の時にはブレイクポイントが挿入されるようになっています。また、`nth()`関数によってリストの何番目の値を取得するかを指定しています。

余白を指定する@mixinです。

```scss
// Vertical Rhythmに対応した余白を返します。
// -----------------------------------------------------
// @type Mixin
// @prop {String} $prop - プロパティ名を渡します。
// @prop {Number} $vr(1) - 指定したい行数を渡します。
// @prop {String} $important(false) - `!important`を指定したい場合は文字列`important`を渡します。
// @requires {Map} $base-font-sizes - rootのフォントサイズと行の高さを定義しているマップです。
// @requires {Map} $breakpoints - ブレイクポイントを管理するためのmapです。
//
// @example scss - Usage
// .foo {
//   @include vertical-rhythm(margin-top, 1);
//   @include vertical-rhythm(margin-bottom, 1);
// }
//
// @example css - CSS output
// .foo {
//   margin-top: 24px;
//   margin-bottom: 24px;
// }
// @media screen and (min-width: 1000px) {
//   .foo {
//     margin-top: 28px;
//   }
// }
// @media screen and (min-width: 1000px) {
//   .foo {
//     margin-bottom: 28px;
//   }
// }
//
@mixin vertical-rhythm($prop, $vr: 1, $important: false) {
    @if ($important == "important") {
        $important: unquote("!important");
    } @else {
        $important: null;
    }
    $bp: $breakpoints;
    @each $name, $unit in $base-font-sizes {
        @if $name == "base" {
            #{$prop}: nth($unit, 2) * $vr $important;
        } @else {
            @if map-has-key($bp, $name) {
                @media #{map-get($bp, $name)} {
                    #{$prop}: nth($unit, 2) * $vr $important;
                }
            }
        }
    }
}
```

第一引数に指定したいプロパティを渡します（ショートハンドは指定できません）。第二引数に行の高さの倍数を渡します。第三引数は`!important`を付けたい場合にだけ文字列の`important`を渡します。

## インクルードする
作成した@mixinの使用例です。

フォントサイズを指定する@mixinの`font-size`はフォントサイズ用のマップを渡して指定します。

```scss
html {
    @include font-size;
}

h1 {
    @include font-size($h1-font-sizes);
}
h2 {
    @include font-size($h2-font-sizes);
}
h3 {
    @include font-size($h3-font-sizes);
}
```

余白を指定する@mixinの`vertical-rhythm`はプロパティと行の高さの倍数を渡します。

```scss
h1 {
    @include vertical-rhythm(margin-top, 2);
    @include vertical-rhythm(margin-bottom, 2);
}

h2, h3 {
    @include vertical-rhythm(margin-top, 2);
    @include vertical-rhythm(margin-bottom, 1);
}

h4, h5, h6,
p,
ul, ol, {
    margin-top: 0;
    @include vertical-rhythm(margin-bottom, 1);
}
```

---

以下の記事を参考にさせていただきました。

* [Responsive Typography with Sass Maps | Smashing Magazine](http://www.smashingmagazine.com/2015/06/responsive-typography-with-sass-maps/)
* [[Sass/SCSS] remを使ったVertical Rhythm | Webデザイン、フロントエンド系の技術に関する備忘録 - whiskers](http://whiskers.nukos.kitchen/2015/05/26/vertical-rhythm-function.html)