モバイルブラウザで画面の縦長・横長を検出する方法を再確認してみた

※ *iOS 8での挙動に関連して追加情報を書いてみました: [Safari for iOS 8での画面回転とイベント](http://qiita.com/tomoyukilabs/items/05f5d886893f79c1a00b)*

# はじめに

モバイルブラウザ(iOS SafariやAndroid標準ブラウザ、Chrome for Androidなど)で、WebアプリのJavaScriptから端末画面が縦長(portrait)と横長(landscape)のどちらを向いているかを調べる方法は、Web上で検索すると色々出てくるのですが、特にAndroid対応を考えると、例えば「[au開発者情報Blog: CSS3 MediaQueries で端末回転方向によるレイアウト切り替えができない](http://devlog.au.kddi.com/article/item-000454/)」辺りに書いてあるように、やってみると上手くいかないことが色々と出てきたりするように思えます。

## 注意事項(参考)

厳密には、`window.orientation`や`orientationchange`イベントは、現在は*[あくまでもベンダ独自仕様扱い](https://compat.spec.whatwg.org/#windoworientation-interface)*として整理されていることに注意が必要です。現在ではChrome, Opera, FirefoxがW3C仕様(ドラフト)の[Screen Orientation API](http://www.w3.org/TR/screen-orientation/)をサポートし、Edgeも古いドラフト仕様のScreen Orientationをサポートしていますので、iOSに限り`orientationchange`を利用する等の対応の方が適切かもしれません。

# 何が厄介なのか

悩ましいポイントとしては、例えばこんなところでしょうか。

1. `orientationchange`イベント、`resize`イベント、実際の画面リサイズ処理が実行される順序がiOS SafariとAndroid向けブラウザとで異なっている。
2. Androidの場合、フォームの文字列入力にフォーカスする等によってソフトウェアキーボードが表示されるときにもリサイズが発生してしまい、縦長画面にもかかわらず横長と判定できてしまうケースがある。
3. Androidの場合、`window.orientation`で取得できる値は、端末の実装上、縦長と横長のどちらを「正面」として扱っているかによって異なっている。
4. Android 2.1以前の標準ブラウザではそもそも`orientationchange`イベントも`window.orientation`プロパティも対応していない。

ひとまず、(初代Xperia等には申し訳ないですが)4.の対応はここでは扱わないこととして、それ以外について考えてみます。

# では、どうするか

まず、1.についてですが、

* iOSの場合は、画面リサイズが完了してから、`orientationchange`や`resize`といったイベントが発生する。
* Androidの場合は、`orientationchange`イベント→画面リサイズ→`resize`イベント、といった順序で処理が行われる。

という流れとなっているため、画面の向きの判定は`resize`イベントのリスナー関数で行えばよい、ということはよく知られています。

ただ、この時にウィンドウの幅と高さ(`window.innerWidth`と`window.innerHeight`、あるいは`screen.width`と`screen.height`)を比較する方法で判別してしまうと、2.の問題にぶつかってしまいます。

では、幅と高さの比較ではなく、`window.orientation`を使おう...と考えるわけですが、今度は3.の問題にぶつかってしまいます。

そこで、**Webアプリの実装で、初期化処理が完了する前にはソフトウェアキーボードが表示されないようにフォーム等を細工しておく**、という前提で、次のような対処を考えてみます。

* 初期化処理(`window`の`load`イベントのリスナ)において、画面の幅・高さと、`window.orientation`の値がどのように対応しているかをチェックする。
* 以後、`resize`イベント発生時に、画面の向きの検出処理を行う。

## 具体的には - サンプルコード

サンプルコード(orientation.js)を次に示します。

```js:orientation.js
var defaultOrientation; // window.orientationが0または180の時に縦長であればtrue

// 初期化処理
window.addEventListener('load', function() {
  if('orientation' in window) {
    var o1 = (window.innerWidth < window.innerHeight);
    var o2 = (window.orientation % 180 == 0);
    defaultOrientation = (o1 && o2) || !(o1 || o2);
    checkOrientation();
  }
  // もしあれば、その他Webアプリの初期化処理
}, false);

// 画面回転時に向きをチェック
window.addEventListener('resize', checkOrientation, false);
function checkOrientation () {
  if('orientation' in window) {
    // defaultOrientationがtrueの場合、window.orientationが0か180の時は縦長
    // defaultOrientationがfalseの場合、window.orientationが-90か90の時は縦長
    var o = (window.orientation % 180 == 0);
    if((o && defaultOrientation) || !(o || defaultOrientation)) {
      // ここに縦長画面への切り替え処理を記述
      console.log('portrait');
    }
    else {
      // ここに横長画面への切り替え処理を記述
      console.log('landscape');
    }
  }
}
```

# ちなみに、今後の動向について

現在、W3Cでは、画面の向きを把握・制御する新しい仕様として、[The Screen Orientation API](http://www.w3.org/TR/screen-orientation/)の仕様策定が進められています。

現状の`window.orientation`プロパティは、実際の画面の向き(縦長/横長)の判定には少々使いにくいくくなっていますが、新しいScreen Orientation APIでは、デバイス上でプライマリの画面の向きがportraitとlandscapeのどちらに定められているかを踏まえて、画面の向きを検出できるようになっています。また、画面の向きをロック/アンロックするメソッドも新たに定義されています。

この新しいAPIは、`window`ではなく`screen`オブジェクトに対する拡張となっている点に注意が必要です。

なお、この仕様は、現時点ではベンダプレフィクス(`moz`, `ms`)付きでFirefox for AndroidとIE11でサポートされているとのことです[(参考: MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Screen.orientation)。