JavaScript
js

【続々】window.matchMedia を用いたブレイクポイントイベント

前回、前々回と改良を重ねてきて、ようやく使いやすい形ができました。

前回 → window.matchMedia を用いたブレイクポイントイベント
前々回 → 続・window.matchMedia を用いたブレイクポイントイベント

あらすじ

前回までのあらすじを書くと、window.matchMedia()addEventListener して監視をするまではよかったですが、ブレイクポイントが変わった時にPCなのかタブレットなのか判断できる材料がないのでかなり困った話でした。

その時は window.innerWidth の差分を取って数値の変化を方向を判断することで乗り切りました。
前発生したイベントよりも large なのか small なのかを判断していました。

課題

  • large とか small とかちょっと実装がイモっぽい
  • メイン実装側でブレイクポイントごとに addEventListener するのは辛い

改修ポイント

上記2点を改善するために色々変わりました。

  • ブレイクポイントが変わった時のデバイスの種類判別をメディアクエリで判別するように

根本を変更したのはこれだけです。
それに付随して下記が変更になりました

  • 発火するイベントの種類を1つにすることができた
  • ブレイクポイントが増えても柔軟にすることができた

解説

メディアクエリの組み立て

設定されるブレイクポイントごとにメディアクエリを作成する。中間のブレイクポイントもメディアクエリとして設定します。

例:480pxと960pxの場合

CSSの場合は下記のような記述で、960px以上、959px〜481px、480px以下の3つの状況が作れました。

@media screen and (min-width:960px) {
  /* 略 */
}
@media screen and (min-width:481px) and (max-width:959px) {
  /* 略 */
}
@media screen and (max-width:480px) {
  /* 略 */
}

これと同じことをJS側でも実装します。配列を some で繰り返し処理して設定するブレイクポイント数分だけメディアクエリを作成します。
変数名をわかりやすく(?)日本語で記載してみました。

ブレイクポイントの配列.some(function (val, index) {
    if (index == 0) {
        メディアクエリ用の配列.push('screen and (max-width: ' + (ブレイクポイントの配列[index + 1] - 1) + 'px)');
    } else if (index == ブレイクポイントの配列.length - 1) {
        メディアクエリ用の配列.push('screen and (min-width: ' + val + 'px)');
    } else {
        メディアクエリ用の配列.push('screen and (max-width: ' + (ブレイクポイントの配列[index + 1] - 1) + 'px) and (min-width: ' + ブレイクポイントの配列[index] + 'px)');
    }
});

こうして出来たメディアクエリ用の配列には下記の値がセットされています。
最初と最後以外では、設定するブレイクポイントの大きい方を -1 してセットしている内容となります。

0:"screen and (max-width: 479px)"
1:"screen and (max-width: 959px) and (min-width: 480px)"
2:"screen and (min-width: 960px)"

あとでこのメディアクエリを元に照会するので、別途クローンして、ついでに正規表現で半角スペースを削除しています。

myquery_cl = [].concat(myquery);

myquery_cl.some(function (val, index) {
    myquery_cl[index] = myquery_cl[index].replace(/\s+/g, "");
});

メディアクエリ名で判断する

実は window.matchMediaaddListener した時に発火した時のメディアクエリ名が取得できます。

window.matchMedia(メディアクエリ用の配列[index]).addListener(function (e) {
    if (e.matches) {
        var tmp = e['media'].replace(/\s+/g, "");
    }

    _.dispatchEvent(break_point_change_event, { width: window.innerWidth, breakpoint: ブレイクポイントの配列[myquery_cl.indexOf(tmp)] });
});

この e['media'] というのがそうです。
中身は発火するたびに下記のように window.matchMedia で登録したメディアクエリ名が入っています。

screen and (max-width: 479px)
screen and (max-width: 959px) and (min-width: 480px)

ポイントなのは、ブラウザによって取得できるメディアクエリ名に半角スペースがあったりなかったりするので、正規表現で削除しています。

あとはメディアクエリの配列とメディアクエリ名を照会して、そのメディアクエリ名があればブレイクポイントの数字をイベントに含めています。
ただ単純に配列に対して、indexOf() しているだけですが。

ブレイクポイントの配列[myquery_cl.indexOf(tmp)]

まとめ

だいぶ駆け足で説明不足感があるかと思います。
コードは GitHub - axcelwork/axia.js にアップロードしていますのでそちらもご覧になってください。