LoginSignup
142
157

More than 5 years have passed since last update.

window.matchMedia をそろそろ活用してもいい頃

Last updated at Posted at 2017-03-13

レスポンシブなページを作っていると、JavaScriptもPC向けとスマホ・タブレット向けで動作を分けたいという事はよくあります。
JavaScriptでレスポンシブ対応させるとしたら以下のような方法があるかと思います。

  • jQuery(window).width() の値から判定
  • window.innerWidth の値から判定
  • window.matchMedia で判定

あと、ここでは Internet Explorer 8 以下に関してはないものとして扱います。

それぞれの動作を見てみる

それぞれにメリット/デメリットがあるのは世の常です。

jQuery(window).width() の値から判定

はっきり言うとこの方法はダメです。何がダメかというと、jQuery(window).width()スクロールバーを含めないウィンドウの横幅になるからです。
CSSのメディアクエリの min-widthmax-widthスクロールバーも含めたウィンドウの横幅で判定しているので十数ピクセルの差が生まれてしまい、思うように動かない原因になります。なので、メリットはありません。

ちなみに、jQuery v3.0.0以降では jQuery(window).outerWidth()window.innerWidth と同じ値を取得できます。

window.innerWidth の値から判定

window.innerWidth はスクロールバーを含むウィンドウの横幅を取得できるプロパティです。高さなら window.innerHeight

これならCSSと切替タイミングが一致するので期待通りの動作を行えます。(あえてjQueryを使わずに書いています)

JavaScript
document.addEventListener('DOMContentLoaded', function () {
  function checkBreakPoint() {
    var width = window.innerWidth;

    if (width <= 780) {
      // スマホ向け
    } else {
      // PC向け
    }
  }

  // リサイズの監視
  window.addEventListener('resize', checkBreakPoint);

  // 初回チェック
  checkBreakPoint();
});

メリット

  • ほとんどのブラウザが対応している。
  • 数値をみるだけなのでとても単純。

デメリット

  • スマホの場合頻繁に onresize が実行される可能性があるので、端末のレスポンスに影響が出る可能性がある。
  • ブレイクポイントで切り替わる時に一度だけ実行したい処理が何度も実行される可能性がある。

スマホに於いての onresize

iPhoneやAndroidのブラウザを使っていると、スクロール時に上部や下部でアドレスバーや操作バーが現れたり隠れたりしますよね。
その時に onresize イベントが毎回発火してしまうので、不必要な実行が繰り返されてしまう可能性があります。

一応その対処として実行を間引くという意味で window.requestAnimationFrame を使う方法などがあります。

JavaScript
document.addEventListener('DOMContentLoaded', function () {
  // 対応してないIE9では setTimeout で代用
  var
    requestAnimationFrame = window.requestAnimationFrame || function (callback) {
      return setTimeout(callback, 1000 / 60);
    },
    cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout,
    requestTimer = null;

  function checkBreakPoint() {
    var width = window.innerWidth;

    if (width <= 780) {
      // スマホ向け
    } else {
      // PC向け
    }
  }

  window.addEventListener('resize', function () {
    cancelAnimationFrame(requestTimer);
    requestTimer = requestAnimationFrame(checkBreakPoint);
  });

  checkBreakPoint();
});

ブレイクポイントで切り替わる時に一度だけ実行させたい

適当なフラグで管理すればいけないこともありません。

JavaScript
document.addEventListener('DOMContentLoaded', function () {
  var
    requestAnimationFrame = window.requestAnimationFrame || function (callback) {
      return setTimeout(callback, 1000 / 60);
    },
    cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout,
    requestTimer = null,
    isSP = false;

  function checkBreakPoint() {
    var width = window.innerWidth;

    if (width <= 780) {
      if (isSP) {
        return;
      }
      isSP = true;

      // スマホ向け
    } else {
      if (!isSP) {
        return;
      }
      isSP = false;

      // PC向け
    }
  }

  window.addEventListener('resize', function () {
    cancelAnimationFrame(requestTimer);
    requestTimer = requestAnimationFrame(checkBreakPoint);
  });

  checkBreakPoint();
});

……なんだかややこしくなってきました。

window.matchMedia で判定

window.matchMedia はCSSと同じ指定ができるメソッドです。返り値は [Object MediaQueryList] になります。

JavaScript
document.addEventListener('DOMContentLoaded', function () {
  // @media screen and (max-width: 780px) と同じ
  var mql = window.matchMedia('screen and (max-width: 780px)');

  function checkBreakPoint(mql) {
    if (mql.matches) {
      // スマホ向け
    } else {
      // PC向け
    }
  }

  // ブレイクポイントの瞬間に発火
  mql.addListener(checkBreakPoint);

  // 初回チェック
  checkBreakPoint(mql);
});

window.matchMedia はメディアクエリの条件から判定するだけでなく、MediaQueryList.addListener() で「クエリを通知する」事ができます。
クエリを通知する、とは、要するに メディアクエリの条件に合うようになった or 合わなくなった 瞬間に実行するという事です。

メリット

  • CSSと同じ書式で指定することができる。
  • 判定方法が簡潔。
  • 切り替わる時に一度だけ実行できる。
  • onresize を使わないので、無駄な関数処理がない。

デメリット

  • Internet Explorer 9 は対応していない。(CSSのメディアクエリは使える)

おまけ:スマホの向きが変わった時に実行する方法

window.matchMedia を使えばCSSと同じくスマホの向きが変わっても簡単に対応できます。

JavaScript
var mql = window.matchMedia('(orientation: portrait)');

function orientationChange(mql) {
  if (mql.matches) {
    // 画面が縦長い時
  } else {
    // 画面が横長い時
  }
}

orientation.addListener(orientationChange);
orientationChange(mql);

注意しないといけないのが、(orientation: portrait) は単純にウィンドウの縦横比をみているだけなので、PCのブラウザでも縦長いウィンドウにした場合にも適用されてしまいます。
スマホ表示の時のみなど、上手く条件分けしてやらないといけないですね。

おわりに

onresize はスマホで扱うには結構厄介なものなので、個人的には使いたくないイベントですね。
window.matchMedia はIE9以下には対応していないのですが、IEでスマホ向け表示をすることはウィンドウ幅を狭くしない限り通常ないと思うので、うまく条件分けしてやればいいかもしれません。

142
157
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
142
157