レスポンシブなページを作っていると、JavaScriptもPC向けとスマホ・タブレット向けで動作を分けたいという事はよくあります。
JavaScriptでレスポンシブ対応させるとしたら以下のような方法があるかと思います。
-
jQuery(window).width()
の値から判定 -
window.innerWidth
の値から判定 -
window.matchMedia
で判定
あと、ここでは Internet Explorer 8 以下に関してはないものとして扱います。
#それぞれの動作を見てみる
それぞれにメリット/デメリットがあるのは世の常です。
##jQuery(window).width() の値から判定
はっきり言うとこの方法はダメです。何がダメかというと、jQuery(window).width()
はスクロールバーを含めないウィンドウの横幅になるからです。
CSSのメディアクエリの min-width
や max-width
はスクロールバーも含めたウィンドウの横幅で判定しているので十数ピクセルの差が生まれてしまい、思うように動かない原因になります。なので、メリットはありません。
ちなみに、jQuery v3.0.0以降では jQuery(window).outerWidth()
で window.innerWidth
と同じ値を取得できます。
##window.innerWidth の値から判定
window.innerWidth
はスクロールバーを含むウィンドウの横幅を取得できるプロパティです。高さなら window.innerHeight
。
これならCSSと切替タイミングが一致するので期待通りの動作を行えます。(あえてjQueryを使わずに書いています)
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
を使う方法などがあります。
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();
});
###ブレイクポイントで切り替わる時に一度だけ実行させたい
適当なフラグで管理すればいけないこともありません。
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]
になります。
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と同じくスマホの向きが変わっても簡単に対応できます。
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でスマホ向け表示をすることはウィンドウ幅を狭くしない限り通常ないと思うので、うまく条件分けしてやればいいかもしれません。