スマホやタブレットでスクリーンの縦横の向きを判定する方法と、画面回転時にどんなイベントが発火するのかを整理していき、どの方法で向きを判定してどのイベントにその判定処理を登録すればよいのか、その組み合わせを考察していこうと思います。
ちゃんと調べてみて初めて分かったのですが、iOSとAndroidで挙動が違ったり、それぞれのバージョンやブラウザでも色々異なる点があり、かなり複雑でややこしいです。
それらの挙動の違いもまとめていきます。(私が調べることができた範囲ですが)
縦横の判定方法
まずは、スクリーンの向きを判定し、縦向きの場合と横向きの場合で処理を分ける方法をメモ。
向きの切り替え時やロード時などに自由に呼び出せるように、関数を定義しておきます。
パターン1 : window.orientationで判定する
window.orientation
を使用すると、画面の向きが通常の向き(正面に設定されている向き)から時計回りにどれだけ回転しているかを取得できる。
値は 0, 90 , 180 , -90 の4パターンあります。が、スマホを縦向きに反転させてもブラウザは逆には向かないので、180は考慮しなくて大丈夫。
function orientCheck(){
//画面の向きを 0,90,180,-90 のいずれかで取得
var orientation = window.orientation;
if (orientation === 0) {
/* 縦画面時の処理 */
} else {
/* 横画面時の処理 */
}
};
問題点 : Androidの場合、window.orientation で取得できる値はそのデバイスが縦横のどちらを「正面」として扱っているかによって異なる。
(横を「正面」としている端末は(たぶん)少数なので、そこまで気にしなくてよさそうですが。)
パターン2 : screen.orientationで判定する
window.orientation
では読み取れなかった「正面」の設定まで考慮できるように、 Screen Orientation APIというものが導入され始めているみたいです。
ブラウザによって少し扱いが異なります。
- Chromeでは、
screen.orientation.type
- Firefoxでは、
screen.mozOrientation.type
- IEでは、
screen.msOrientation.type
によって、「現在の向き」と「その向きが正面設定かどうか」を文字列で取得できます。
取得できる値は以下の4パターン。
portrait-primary
: 「縦長が正面」とされている端末で「縦長」になっている。portrait-secondary
: 「横長が正面」とされている端末で「縦長」になっている。landscape-primary
: 「横長が正面」とされている端末で「横長」になっている。landscape-secondary
: 「縦長が正面」とされている端末で「横長」になっている。
つまり、正面設定にかかわらず、 portrait-○○
であれば縦向き、 landscape-○○
であれば横向きということが判定できます。
function orientCheck(){
//正面設定と現在の向きを取得
var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;
if (orientation.type === "portrait-primary" || orientation.type === "portrait-secondary") {
/* 縦画面時の処理 */
} else {
/* 縦画面時の処理 */
}
};
問題点 : まだ開発段階。iOS版 Safari や chrome で未対応。
パターン3 : 画面サイズの比較で判定する
単純に画面の縦の長さ(window.innerHeight
)と横の長さ(window.innerWidth
)を比較し、どちらの向きか判定する方法です。
function orientCheck(){
if (window.innerHeight > window.innerWidth) {
/* 縦画面時の処理 */
} else {
/* 横画面時の処理 */
}
};
回転時のイベント発火方法
次に、画面の向きによって変えたい処理をどのイベントに登録させるかです。
画面の向きが変化した時に発火するイベントは、2つあります。
- orientationchangeイベント
- resizeイベント
パターン1 : orientationchangeイベントに登録する場合
jQueryを使う場合、使わない場合でのそれぞれの記述方法は以下の通りです。
jQueryを使用する
$(window).on("orientationchange", function() {
/* 向き切り替え時の処理 */
});
jQueryを使用しない
window.addEventListener("orientationchange", function() {
/* 向き切り替え時の処理 */
});
パターン2 : resizeイベントに登録する場合
jQueryを使う場合、使わない場合でのそれぞれの記述方法は以下の通りです。
jQueryを使用する
$(window).on("", function() {
/* 向き切り替え時の処理 */
});
jQueryを使用しない
window.addEventListener("resize", function() {
/* 向き切り替え時の処理 */
});
resizeイベントの注意点:スマホのブラウザではスクロールしてもurlバーが引っ込んだり出てきたりするときに発動してしまうので、「横幅が変わったときにだけ」という条件分岐が追加で必要。また、画面の向き切り替え時には複数回発火してしまう。
縦横の判定方法と登録先イベントの組み合わせ方によっては問題がある
先に紹介した3パターンの「縦横の判定方法」で取得できる値ですが、各イベントの発火時に「すでに向きが切り替わった状態」の値なのか、「まだ向きが切り替わっていない状態」の値なのか、それぞれ値の更新タイミングが異なります。
つまり、イベントの組み合わせ方によっては、不具合が出てくるケースがあります。
そのパターンをまとめていきます。
*Screen Orientation APIをメインにして画面の向きを判定するパターンは除外しています。(既に述べた通り、現状ではまだ開発段階であるため)
window.orientationでの向き判定 × orientationchangeイベントへ登録
- 問題なく画面回転後の値が取得できる。(iOS10とAndroid7.0で実機確認済み)
- 「window.orientationでの向き判定」自体が問題点として抱える、Androidでの正面問題のみ考慮しなければならない。
window.orientationでの向き判定 × resizeイベントへ登録
- iOS8,9ではresizeイベント時にはまだ window.orientation の値が更新されない。(iOS10では大丈夫だった)
- Androidの古いバージョンでも同様。(Android7.0では大丈夫だった)
- そもそもresizeイベントが扱いにくい
Androidがどのバージョンまでこうなのかは詳しくわかりません。 こちらの記事は2017年のものですが4.4.2を使用しており、同様の状態です。
画面サイズ比較での向き判定 × orientationchangeイベントへ登録
- Androidでは「orientationchangeイベント発火→画面回転(縦横サイズのリセット)→resizeイベント発火」、といった順序で処理が行われるので、orientationchangeイベント時に画面サイズを取得しても回転前のものになってしまう。
- iOS10でも同様に、oientationchangeイベント時に画面サイズを取得しても回転前のものだった。(それ以前はバージョンによって変わるっぽい)
画面サイズ比較での向き判定 × resizeイベントへ登録
- Androidの場合、フォーム入力時など、ソフトウェアキーボードが表示されるときにもリサイズが発生してしまう。
この時、なぜか縦向きにもかかわらず横向き(横幅の値の方が大きい)と判定できてしまうケースがあるようです。 - そもそもresizeイベントが扱いにくい
結論:最適な向き判定方法と登録先イベントの組み合わせはどれがいいのか
window.orientationで向きの判定を行い、orientationchangeイベント発火時に処理を行う方法が一番無難っぽい。
どうしてもAndroidでの正面問題もカバーしたい場合、Andoroidの場合のみScreen Orientation APIを使用してロード時に正面設定を確認しておけば良い。
例:正面問題も考慮した場合のコード
$(function(){
var isReverse = false; //正面が逆かどうかのフラグを用意しておく
if (navigator.userAgent.indexOf('Android') > 0) {
//Androidなら正面設定を確認
var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;
if (orientation.type === "portrait-secondary" || orientation.type === "landscape-primary") {
isReverse = true;
}
}
$(window).on("orientationchange", function() {
if(isReverse){
orientCheckReverse(); //正面が逆の場合
}else{
orientCheck(); //正面が通常の場合
}
});
});
//正面設定が通常の場合の縦横判定処理
function orientCheck() {
var orientation = window.orientation;
if (orientation === 0) {
/* 縦画面時の処理 */
} else {
/* 横画面時の処理 */
}
};
//正面設定が逆の場合の縦横判定処理
function orientCheckReverse() {
var orientation = window.orientation;
if (orientation === 0) {
/* 横画面時の処理 */
} else {
/* 縦画面時の処理 */
}
};
おまけ:向きによってcssの読み込みを変える方法
cssを読み込むlinkタグ ですが、次のような使い方もできるみたいです。
*動作状況など、詳しいことは何も調べてないです。
<!-- 縦の場合に読み込むスタイルシート -->
<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css">
<!-- 横の場合に読み込むスタイルシート -->
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css">
コメント
コメント一覧 (4件)
orientCheck と orientCheckReverse の記述が同じになってはいませんか?
ご指摘ありがとうございます。
修正致しました!
「パターン2 : screen.orientationで判定する」における取得できる値の説明に違和感があります。
W3C「The Screen Orientation API」の「5.2 Reading the screen orientation」 https://www.w3.org/TR/screen-orientation/#reading-the-screen-orientation では、「Natural Orientation(正面)」が「Portrait(縦)」でも「Landscape(横)」でも出現するとなっています。
「portrait-○○」であれば縦向き、という部分は間違っていませんが、この値だけで「正面」が「縦」か「横」かの判定は出来ません。
「結論:最適な向き判定方法と登録先イベントの組み合わせはどれがいいのか」における「例:正面問題も考慮した場合のコード」では、isReverseの値はScreenOrientation.typeだけで判定しています。
この場合、「正面」が「縦」のAndroid端末であっても、画面を横向きにした状態でScriptを読み込んだ際、「正面」が「横」と判定されることになり、処理が縦横反対になってしまいます。
「正面」かどうかの判定は、ScreenOrientation.angleと合わせる必要があるのではないでしょうか。
> 値は 0, 90 , 180 , -90 の4パターンあります。が、スマホを縦向きに反転させてもブラウザは逆には向かないので、180は考慮しなくて大丈夫。
iPad等、主にタブレット端末では天地逆の180になる機種も結構ありますよ。