mediaQueryの記述を効率化するためのラッパーを npm package で作ってみた

# はじめに

毎回微妙に違うけど似たようなロジックを、プロジェクト毎に組んでしまいがちです。
どうせいつも使うなら、自分用に `npm package` 化し、方針変更をバージョンアップで受け取れるようにしておく方が、理にかなってるような気がしたので、今回学習と実験を兼ねて作ってみました。

簡単に作れそうなものとして、css の `MediaQueries` を題材に選んでみました。

おそらく似たようなpackageは大量に存在しているとは思っていますが、自力で車輪を発明することも今回の趣旨なので、ご容赦を。

## 大枠の方針

- `link要素`での使用は禁止。
- `.class`内で細かく分岐させる。
- breakpointは 任意の数を定数で管理。プロジェクトに応じて簡単にoverride可能にする。
- MediaQueries用のラッパー関数を中継して記述。生では書かない。
- mobile first / desktop first のどちらの方針も対応できる。
- `window.matchMedia`を使い、可能な限り似たロジックで`js`版もつくる。

## 今回作った2つの npm package

- https://www.npmjs.com/package/@rm-labo/mq-scss
- https://www.npmjs.com/package/@rm-labo/mq-js

scss用とjs用として2つに分割して作成。
ほぼ自分用の側面が強いので名前を `scoped modules` にします。

（統合する方針で当初構想したが、そもそも、 MediaQueries 管理をしないと行けない状況は最終手段感がある、 命名が完全には揃えられない、使うべき理由がそもそも大きく異なるはず、等の理由で分けて扱う事に。）

原則scssで完結できる世界を願い、jsの方はおまけです。


 
# mq-scssの方針

cssコーディングは、絵を扱うという性質上、まま理不尽な状況が多い気がします。
また webサイト なのか webアプリ なのかといった違いで、求められるブレイクポイントの粒度が全然異なります。
これらの事情を鑑み、安全に運用するために、`グローバルなbreakpoint管理` と `イレギュラーな状況に対応できる` の2点を同時に担保できる方法を目指します。

## インストール

```bash
$ npm i -D @rm-labo/mq-scss
```

```scss
@import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';
```

JavaScriptのエントリポイントからCSSを処理する場合は、スタイルシートをモジュールとして直接インポート。

```javascript
import '@rm-labo/_mq.scss'
```

## 使い方

3種類 * 2方向の@mixinsを、キャメルケースかケバブケースで使えるようにした。

任意の箇所でで使うときは以下。各mixinは以下の場面で使う。

- `mq-{'width'|'height'}-up($bp) {}` : スマホファーストの記述用
- `mq-{'width'|'height'}-down($bp) {}` : デスクトップファーストの記述用
- `mq-{'width'|'height'}-between($bp1, $bp2) {}` : 特定範囲用

```scss
// CamelCase or kebab-case
.class-name {
  // width camelCase 
  @include mqUp($bp) {}
  @include mqDown($bp) {}
  @include mqBetween($bp1, $bp2) {}
 
  // width kebab-case (same as camelCase) 
  @include mq-up($bp) {}
  @include mq-down($bp) {}
  @include mq-between($bp1, $bp2) {}
  
  // height camelCase 
  @include mqHeightUp($bp) {}
  @include mqHeightDown($bp) {}
  @include mqHeightBetween($bp1, $bp2) {}
 
  // height kebab-case (same as camelCase) 
  @include mq-height-up($bp) {}
  @include mq-height-down($bp) {}
  @include mq-height-between($bp1, $bp2) {}
}
```

| arguments | type                | required | default value | description                              |
| --------- | ------------------- | -------- | ------------- | ---------------------------------------- |
| $bp       | `string` , `number` | `false`  | `md`          | `sm`, `md`, `lg`, `xl`, `Free px number` |
| $bp1      | `string` , `number` | `true`   | -             | `sm`, `md`, `lg`, `xl`, `Free px number` |
| $bp2      | `string` , `number` | `true`   | -             | `sm`, `md`, `lg`, `xl`, `Free px number` |

全体で共用するbreakpointが!defaultで定義されている。

```scss
// デフォルト値
$mq-breakpoints: (
  sm: 600px,
  md: 900px, // default
  lg: 1200px,
  xl: 1800px
) !default;
$mq-breakpoints-default-key: 'md' !default;
```
プロジェクトに合わせてoverrideして使う。

```scss
// override例
$mq-breakpoints: (
  micro: 320px,
  small: 620px,
  medium: 840px, // default
  large: 1280px,
  extra: 1900px,
  // .. 好きなだけ追加 ..
);
$mq-breakpoints-default-key: 'medium';

@import 'path/to/node_modules/@rm-labo/mq-scss/_mq.scss';
```

基本的にいはスマホファースト且つ、$breakpointsで定義した共用の値のkey `sm` `md` `lg` `xl` を引数として受け取るが、その一箇所でしか使わないイレギュラーケース用として、ピクセル値による指定も許容するよう実装する。
また、PC/スマホだけのような1ブレイクポイントの簡易的な実装も多いため、省略してスッキリかけるようにも対応する。


```scss
.className {

  // SINGLE BREAK POINT
  @include mqUp() { /* 900px (default: md) and up */ }
  @include mqDown() { /* 900px (default: md) and down */ }
  
  // MOBILE FIRST POLICY 
  @include mqUp('sm') { /* 600px and up */ }
  @include mqUp(900px) { /* 900px and up */ } // イレギュラーな px数でもOK
  @include mqUp('lg') { /* 1200px and up */ }
  @include mqUp('xl') { /* 1800px and up */ }
  
  // DESKTOP FIRST POLICY 
  @include mqDown('xl') { /* 1799px and down */ }
  @include mqDown(1200px) { /* 1199px and down */ } // イレギュラーな px数でもOK
  @include mqDown('md') { /* 899px and down */ }
  @include mqDown('sm') { /* 599px and down */ }
 
  // SPECIFY BY RANGE  
  @include mqBetween('sm', 'md') { /* 600px ~ 899px */ }
  @include mqBetween('sm', 'lg') { /* 600px ~ 1199px */ }
  @include mqBetween('sm', 1600px) { /* 600px ~ 1599px */ } // 混在OK
  @include mqBetween(1234px, 850px) { /* 850px ~ 1233px */ } // 大小逆もOK
} 

```

# mq-jsの方針

原則cssで完結するべきだが、どうしてもjsによるサポートが必要なケースもある。
そのため、scssとなるべく近いコンセプトのjsもおまけで作ってみた。

[window.matchMedia](https://developer.mozilla.org/ja/docs/Web/API/Window/matchMedia) を使い、ブレイクポイントのkeyの名前とcallbackを受け取るようなラッパーを生成する方針。


## インストール

```bash
$ npm i -D @rm-labo/mq-js
```

```javascript
import Mq from '@rm-labo/mq-js'
```

## 使い方

読み込んでインスタンスを作り、それに対して以下のようなメソッドを実行していきます。
当たり前ですがjsなので kebab-case はありません。

```javascript
import Mq from '@rm-labo/mq-js'
const mq = new Mq()

mq.up( bpKeyName1, matchHandler [, unmatchHandler ])
mq.down( bpKeyName1, matchHandler [, unmatchHandler ])
mq.between( bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])

```
パラメータは以下

| arguments      | type       | required | 
| -------------- | ---------- | -------- | 
| bpKeyName1     | `string`   | `true`   | 
| bpKeyName2     | `string`   | `true`   | 
| matchHandler   | `function` | `true`   | 
| unmatchHandler | `function` | `false`  | 


```javascript
// 単一ブレイクポイントの場合
mq.up(
  'sm', 
  () => { /* Fires in 600px and up */ },
  () => { /* Fires in 599px and down */ }
)

// 別の明示的な書き方もできるように
mq
  .down('md', () => { /* Fires in 899px and down */ })
  .up('md', () => { /* Fires in 900px and up */ })

// 特定の範囲だけを対象にしたい場合
mq
  .between(
    'md', 
    'lg', 
    () => { /* Fires in 900 ~ 1199px */ },
    () => { /* Fires in ~ 899px and 1200px ~ */ })

// すべての範囲を網羅したい場合はメソッドチェーンでつなげる
mq
  .down('sm', () => { /* Fires in 599px and down */ })
  .between('sm', 'md', () => { /* Fires in 600 ~ 899px */ })
  .between('md', 'lg', () => { /* Fires in 900 ~ 1199px */ })
  .between('lg', 'xl', () => { /* Fires in 1200 ~ 1799px */ })
  .up('xl', () => { /* Fires in 1800px ~ */ })

```
scssと同様で高さを対象にできるように。

```javascript
mq
  .heightUp(bpKeyName1, matchHandler [, unmatchHandler ])
  .heightDown(bpKeyName1, matchHandler [, unmatchHandler ])
  .heightBetween(bpKeyName1, bpKeyName2, matchHandler [, unmatchHandler ])
  
```

ブレイクポイントもoprion経由でoverrideできるようにする。


```javascript
// 例
const option = {
  breakpoints: {
    'micro' : 320,
    'small' : 620,
    'medium' : 840,
    'large' : 1280,
    'extra' : 1900,
    // .. 好きなだけ追加 ..
  }
}
const mq = new Mq(option)
mq
  .down('micro', () => { /* Fires in 319px and down */ },)
  .between('micro', 'small', () => { /* Fires in 320 ~ 619px */ },)
  .between('small', 'medium', () => { /* Fires in 620 ~ 839px */ },)
  .between('medium', 'large', () => { /* Fires in 840 ~ 1279px */ },)
  .between('large', 'extra', () => { /* Fires in 1280 ~ 1899px */ },)
  .up('extra', () => { /* Fires in 1900px and up */ },)
```

# demo

実際に動かしているページも一応用意した。

- https://rm-labo.github.io/mq-scss/
- https://rm-labo.github.io/mq-js/


もう少しスマートな方法もありそうではあるが、一旦これを使ってみる。


# 課題

pxベース限定になっている点。
emベースなど別の単位で管理したいケースもあるが今回は実装を見送った。バージョンアップで対応するかもしれない。
(em等、px以外の単位で MediaQuery を書きたい場合に必要なcalc計算が、一部レガシーブラウザ待ちの状況。）