Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

レスポンシブなページを作っていると、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でスマホ向け表示をすることはウィンドウ幅を狭くしない限り通常ないと思うので、うまく条件分けしてやればいいかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした