JavaScriptでもメディアクエリが使えるwindow.matchMediaを関数化して使い回す

## 要約
- [window.matchMedia](https://developer.mozilla.org/ja/docs/Web/API/Window/matchMedia)はメディアクエリ文字列を渡して、その条件が切り替わった時にだけ処理を実行してくれます
- いわゆる`debounce()`などで`resize`イベントの間引きをする必要がありません（画面の横幅を使う処理は`resize`イベントが引き続き適切です）
- IE10からサポートされているので、ブラウザの対応範囲を気にする必要はありません（[matchMedia \| Can I use\.\.\. Support tables for HTML5, CSS3, etc](https://caniuse.com/matchmedia)）
- コールバックを実行する関数にして、1箇所でメディアクエリ文字列を管理できます

## ソースコード
- [real\-world\-website\-boilerplate/mediaQuery\.js](https://github.com/yuheiy/real-world-website-boilerplate/blob/master/src/js/mediaQuery.js)と[pwa\-helpers/media\-query\.ts](https://github.com/Polymer/pwa-helpers/blob/master/src/media-query.ts)を元にしています
- class構文の静的メソッドに変更していますが、`const mediaQuery = (query, layoutChangedCallback) => {}`のように関数式にしても問題ありません
- Babelなどでコンパイルして使ってください

```js
/**
 * `em`に変換するための基準になるフォントサイズ。
 * @type {number}
 */
const BROWSER_DEFAULT_FONT_SIZE = 16;

/**
 * メディアクエリ変数。キーと値はCSSの変数と合わせます。
 * @type {object}
 */
const list = {
  sm: 375,
  md: 768,
  lg: 1024,
  xl: 1440,
};

/**
 * メディアクエリ変数を管理して、`this.matches(query, layoutChangedCallback)`で`query`が一致するか判定します。
 * @example
 * import MediaQuery from './lib/MediaQuery';
 */
export default class MediaQuery {
  /**
   * メディアクエリがロードで判定またはリサイズで判定が切り替わったときに、コールバックで真偽値を返します。
   * @link https://github.com/yuheiy/real-world-website-boilerplate/blob/master/src/js/mediaQuery.js
   * @param {(String | Number)} query - メディアクエリ文字列（`list`のキー）か数値
   * @param {boolean} layoutChangedCallback - メディアクエリが一致するかの真偽値
   * @example
   * MediaQuery.matches('md', matches => {
   *   console.log(matches ? 'md' : 'sm');
   * });
   * MediaQuery.matches(768, matches => {
   *   console.log(matches ? '768以上' : '767以下');
   * });
   */
  static matches(query, layoutChangedCallback) {
    let mediaQuery = `(min-width: ${list[query] / BROWSER_DEFAULT_FONT_SIZE}em)`;

    if (typeof query === 'number') {
      mediaQuery = `(min-width: ${query / BROWSER_DEFAULT_FONT_SIZE}em)`;
    }

    const mql = window.matchMedia(mediaQuery);
    const listener = event => {
      layoutChangedCallback(event.matches);
    };
    mql.addListener(listener);
    layoutChangedCallback(mql.matches);
    const uninstall = () => {
      mql.removeListener(listener);
    };
    return uninstall;
  }
}
```

## 使い方
メディアクエリ文字列（横幅のみ）を`list`変数に設定します。

```js
/**
 * メディアクエリ変数。キーと値はCSSの変数と合わせます。
 * @type {object}
 */
const list = {
  sm: 375,
  md: 768,
  lg: 1024,
  xl: 1440,
};
```

メディアクエリの条件は`min-width`です。

```js
    let mediaQuery = `(min-width: ${list[query] / BROWSER_DEFAULT_FONT_SIZE}em)`;

    if (typeof query === 'number') {
      mediaQuery = `(min-width: ${query / BROWSER_DEFAULT_FONT_SIZE}em)`;
    }
```

[「Sass MQ」](https://www.tam-tam.co.jp/tipsnote/html_css/post17091.html)のようなメディアクエリのライブラリを使っていたり、[自作のメディアクエリmixin](https://www.tam-tam.co.jp/tipsnote/html_css/post10708.html)を使っている場合でも、以下のようにブレイクポイント変数を使っていると思うのでそれに合わせてください。

```scss
$breakpoints: (
  'sm': 'screen and (min-width: 375px)',
  'md': 'screen and (min-width: 768px)',
  'lg': 'screen and (min-width: 1024px)',
  'xl': 'screen and (min-width: 1140px)',
) !default;
```

`md`（768px以上）になったら`func()`を実行する例です。
静的メソッドにしているので、`new MediaQuery();`のようにインスタンスの生成は必要ありません。

```js
import MediaQuery from './lib/MediaQuery';

MediaQuery.matches('md', matches => {
  if (matches) {
    func();
  }
});
```

`md`などのキーだけでなく横幅（px）を直接指定しても動作します。

```js
import MediaQuery from './lib/MediaQuery';

MediaQuery.matches(768, matches => {
  if (matches) {
    func();
  }
});
```