Media Queries（メディアクエリ）とJavaScriptの連携について

レスポンシブのサイトの制作を行っていると、PCとTABとSPのそれぞれのレイアウトでそれぞれ別々のJSの動作をさせたいということがあると思います。それを実現するためにはJavaScript側から今どのレイアウトになっているのかという事を知る必要があります。

そこで今回はJavaScriptでのメディアクエリの判定方法についてご紹介します。
まずはやってしまいがちなNGパターン、続いてOKパターンをいくつかご紹介します。

<ul>
  <li>
    NGパターン
    <ul>
      <li>jQueryのwidthメソッドを使用して判定</li>
    </ul>
  </li>
  <li>
    OKパターン
    <ul>
      <li>window.innerWidthプロパティを使用して判定</li>
      <li>window.matchMediaメソッドを使用して判定</li>
      <li>PC/SP時に表示/非表示にするコンテンツの表示/非表示の状態を読み取って判定</li>
      <li>contentの値を使用して判定</li>
      <li>font-familyの値を使用して判定</li>
    </ul>
  </li>
</ul>

#NGパターン
まずはやってしまいがちなNGパターンからご紹介します。

##jQueryのwidthメソッドを使用して判定
以下のようにjQueryのwidthメソッドを使用して実装をした場合です。

```javascript:JavaScript
var $win = $(window);

$win.on('load resize', function() {
  var windowWidth = $win.width();

  if (windowWidth > 1024) {
    // PCの処理
  } else if (windowWidth > 768) {
    // TABの処理
  } else {
    // SPの処理
  }
});
```

この実装がNGな理由はjQueryのwidthメソッドで取得できるウィンドウサイズがスクロールバーの幅を含まない為です。

CSS側のブレイクポイントはスクロールバーの幅を含んだウィンドウサイズで切り替わるため、CSS側のブレイクポイントの切替とJS側の処理の切替にズレが発生し、CSSはTABのレイアウトになったのにJSはSPの動作をしてしまうといった事が起きてしまいます。

### サンプル
https://jsfiddle.net/5629h86y/10/

Macだとスクロールバーが幅を持たないデザインとなっており問題が発生しないので、WindowsのIEのように常にスクロールバーが出るブラウザで見てみて下さい。ブレイクポイント付近でウィンドウサイズを変更しているとJS側とCSS側でスクロールバーの幅分判定がズレてしまうことが確認できるかと思います。


#OKパターン
次に各種OKパターンをそれぞれメリット/デメリットを混じえつつご紹介します。

## window.innerWidthプロパティを使用して判定
window.innerWidthプロパティを使用するとスクロールバー幅を含んだウィンドウサイズが取得できるため、JS側とCSS側で差異のでない、期待取りの動作をさせる事ができます。

```javascript:JavaScript
var $win = $(window);

$win.on('load resize', function() {
  var windowWidth = window.innerWidth;
  
  if (windowWidth > 1024) {
    // PCの処理
  } else if (windowWidth > 768) {
    // TABの処理
  } else {
    // SPの処理
  }
});
```

### サンプル
https://jsfiddle.net/5629h86y/11/

### メリット
<ul>
  <li>スクロールバーの幅に影響を受けずに期待通りの動作をする事ができる。</li>
  <li>安定して無難に判定が行える。</li>
</ul>

### デメリット
<ul>
  <li>もしCSS側でブレイクポイントを変更する場合、JSのソースコード側の判定幅の数値も合わせて変更する必要があるため、若干保守性が劣る。<br />
※例えばCSSに記述しているブレイクポイントを1024pxから1200pxに変更した場合、JS側の判定式も合わせて1024から1200に変更する必要が発生する。</li>
  <li>IE8以下では使用できない</li>
</ul>




## window.matchMediaメソッドを使用して判定
モダンブラウザやIE10以上のブラウザではwindow.matchMediaというメソッドが使用可能で、これを使用することでCSS側に記述するブレイクポイントの記述と同じような形でJS側の判定条件を記述する事ができます。

```javascript:JavaScript
var	$win = $(window);

$win.on('load resize', function() {  
  if (window.matchMedia('(max-width:480px)').matches) {
    // SPの処理
  } else if (window.matchMedia('(max-width:768px)').matches) {
    // TABの処理
  } else {
    // PCの処理
  }
});
```

### サンプル
https://jsfiddle.net/5629h86y/14/

### メリット
<ul>
  <li>window.innerWidthを使用した場合と同じく期待通りの動作をする事ができる。</li>
  <li>さらにCSS側と同じような記述ができる為、直感的に分かりやすい。</li>
</ul>

### デメリット
<ul>
  <li>デメリットもwindow.innerWidthを使用した場合と同じくCSS側のブレイクポイントを変更する場合、JS側も合わせて変更する必要がある。</li>
  <li>IE9以下では使用できない</li>
</ul>


## PC/SP時に表示/非表示にするコンテンツの表示/非表示の状態を読み取って判定
PCでは表示しない要素に.pc-dn（名前は何でも良いです）というPCでdisplay: none;となるclassを付与し、SPでは表示しない要素に.sp-dn（こちらも名前は何でも良いです）というclassを付与してページを制作する場合に、JS側から.pc-dnが付与されている要素、もしくは.sp-dnが付与されている要素の状態を取得して現在のレイアウトを判定する方法。

```html:HTML
<div class="jsc-pc-dn pc-dn">
  // PCでは表示しない要素
</div>
<div class="jsc-sp-dn sp-dn">
  // SPでは表示しない要素
</div>
```

```css:CSS
/* PCレイアウト用 */
.pc-dn {
  display: none;
}
.sp-dn {
  display: block;
}

/* SPレイアウト用 */
@media screen and (max-width: 768px) {
  .pc-dn {
    display: block;
  }
  .sp-dn {
    display: none;
  }
}
```

```javascript:JavaScript
var $win = $(window),
    $spDn = $('.jsc-sp-dn');

$win.on('load resize', function() {
  if ($spDn.is(':visible')) {
    // PCの処理
  } else {
    // SPの処理
  }
});
```

### サンプル
https://jsfiddle.net/kjtx1nz4/2/

### メリット
<ul>
  <li>CSS側のブレイクポイントを変更しても、JS側は特に変更を加える必要がない。</li>
</ul>

### デメリット
<ul>
  <li>表示/非表示の2択で判定を行うため、PC/SP以外にTAB等のレイアウトが入ってきた場合判定ができなくなる。</li>
  <li>.pc-dnを付与していてSPではdisplay: block;になるハズなのだが、何かのミスでdisplay: none !important;等で上書きされてしまうと誤判定を起こす。</li>
  <li>.is(':visible')で判定する場合、.pc-dnを付与している要素がSPで正しくdisplay: block;になっていたとしても、.pc-dnを付与している要素の親要素がdisplay: none;になっていると要素としてはdisplay: block;なのだがブラウザ上の表示は非表示という状態になり、誤判定を起こす。</li>
</ul>



## contentの値を使用して判定
contentの値に任意の文字列を入れられることを利用します。
※ただしIE、edgeでは使用できません。
※Mac Safariとその他モダンブラウザで文字列を取得した際に「"」が含まれるかどうかの差がある為、「"」を除去しつつ値を取得する必要があります。

```html:HTML
<!-- ページ内のどこかに空タグで良いので配置しておく -->
<div class="jsc-media-queries media-queries"></div>
```

```css:CSS
/* PC用 */
.media-queries {
  display: none;
  content: 'pc';
}

/* TAB用 */
@media screen and (max-width: 768px) {
  .media-queries {
    content: 'tab';
  }
}

/* SP用 */
@media screen and (max-width: 480px) {
  .media-queries {
    content: 'sp';
  }
}
```

```javascript:JavaScript
var $win = $(window),
    $mediaQueries = $('.jsc-media-queries');

$win.on('load resize', function() {
  // Mac Safariでは「pc」という文字列が取得できるが、
  // その他モダンブラウザでは「"pc"」となってしまうため、「"」を除去しつつ取得する。
  var layout = $mediaQueries.css('content').replace(/"/g, '');

  if (layout === 'pc') {
    // PCの処理
  } else if (layout === 'tab') {
    // TABの処理
  } else {
    // SPの処理
  }
});
```

### サンプル
https://jsfiddle.net/5629h86y/12/

### メリット
<ul>
  <li>（デメリットで後述しますがIEとedge以外では）期待通りの動作をする。</li>
  <li>JS側での判定文に「pc」や「sp」といった文字列を使用できるため、コメント文が無くてもコードだけでどのレイアウトの判定なのかを理解できる。</li>
  <li>また、CSS側のブレイクポイントを変更してもJS側は特に修正が不要。</li>
</ul>

### デメリット
<ul>
  <li>IEだと取得できる値が常に「normal」という文字列に、edgeでは何も取得できない為、IEとedgeではレイアウトの判定ができない。</li>
</ul>
※PCで閲覧する分にはレスポンシブになっている必要が無い案件もあると思いますので、その場合はもちろん問題ありません。


### font-familyの値を使用して判定
font-familyの値に任意の文字列が入れられることを利用します。
※モダンブラウザとIEで文字列を取得した際に「"」が含まれるかどうかの差がある為、「"」を除去しつつ値を取得する必要があります。

```html:HTML
<!-- ページ内のどこかに空タグで良いので配置しておく -->
<div class="jsc-media-queries media-queries"></div>
```

```css:CSS
/* PC用 */
.media-queries {
  display: none;
  font-family: 'pc';
}

/* TAB用 */
@media screen and (max-width: 768px) {
  .media-queries {
    font-family: 'tab';
  }
}

/* SP用 */
@media screen and (max-width: 480px) {
  .media-queries {
    font-family: 'sp';
  }
}
```

```javascript:JavaScript
var $win = $(window),
    $mediaQueries = $('.jsc-media-queries');

$win.on('load resize', function() {
  // モダンブラウザでは「pc」という文字列が取得できるが、
  // IEでは「"pc"」と「"」が付与されて取得されてしまうため「"」を除去しつつ取得する。
  var layout = $mediaQueries.css('font-family').replace(/"/g, '');

  if (layout === 'pc') {
    // PCの処理
  } else if (layout === 'tab') {
    // TABの処理
  } else {
    // SPの処理
  }
});
```

### サンプル
https://jsfiddle.net/5629h86y/13/


### メリット
<ul>
  <li>IEやedgeも含めたあらゆる条件で期待通りの動作をする。</li>
  <li>JS側での判定文に「pc」や「sp」といった文字列を使用できるため、コメント文が無くてもコードだけでどのレイアウトの判定なのかを理解できる。</li>
  <li>また、CSS側のブレイクポイントを変更してもJS側は特に修正が不要。</li>
</ul>

### デメリット
<ul>
  <li>font-familyの本来の使い方ではないので実装が超気持ち悪い。</li>
</ul>


# まとめ
様々な方法をメリット/デメリットと共にご紹介しましたが、メリット/デメリットを踏まえた上で案件にあったものを上手く使っていけば良いと思います。