ブラウザがバックグラウンド回ったことを検知する

Android,iOSでホームボタンなどが押されブラウザが後ろに回ったことを
検知したかった。

### ■対象
Android 4.0〜
iOS 7.1.2〜

### ■検証
・visibilitychange ... [mdn](https://developer.mozilla.org/ja/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API)　[Can I use](http://caniuse.com/#search=pagevisibility)
・blur
・onpagehide
・requestAnimationFrame ... [mdn](https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame)　[Can I use](http://caniuse.com/#feat=requestanimationframe)

visibilitychange が使える環境なら、それを優先して使う。
blur は使える環境が広いが、ブラウザメニューを開くなどの行動でも発火してしまうので、優先度は低め。


### ■検証結果

iOS

|  | visibilitychange | blur | onpagehide |
|:-:|:-:|:-:|:-:|
|7.1.2|△※1|×|○|
|8.4.1|○|○|×|○|
|9.3.2|○|○|×|○|

※1 ... 別タブ移動は検知できるが、アプリのバックグラウンド検知は×

Android（標準ブラウザ）

|  | visibilitychange | blur | onpagehide | requestAnimationFrame |
|:-:|:-:|:-:|:-:|:-:|
|F-08(4.0.3)|×|○|×|×|
|DMO-14SH(4.0.4)|×|○|×|×|
|Nexus S(4.1.2)|×|○|×|×|
|F-07E(4.2.2)|×|○|×|×|
|SO-02F(4.4.2)|×|○|×|×|
|SO-01F(4.4.2)|○|?※3|?※3|?※3|
|SH-04F(5.0.2)|○|?※3|?※3|?※3|
|SC-04E(5.0.1)|○|?※3|?※3|?※3|
|SO-03F(5.0.2)|△※2|△※1|×|△※1|
|F-02G(5.0.2)|△※2|△※1|×|×|
|SO-05F(5.0.2)|△※2|△※1|×|△※1|
|SC-01H(6.0)|△※2|?※3|×|○|
|SO-03G(6.0)|△※2|?※3|×|○|
|F-03H(6.0.1)|○|?※3|?※3|?※3|
|SO-02G(6.0.1)|△※2|?※3|×|○|
|SO-04G(6.0.1)|△※2|?※3|×|○|

※1 ... 別タブ移動は検知できるが、アプリのバックグラウンド検知は×
※2 ... Page Visibility API が使える分岐に入るが、ホーム遷移で発火しない
※3 ... 安定して検知できる方法があったので、未検証

標準ブラウザがなく、 ブラウザが chromeの場合は visibilitychange が動くので
上記の表から外しました。

### ■まとめ

iOS 7 → onpagehide
iOS 8〜 → visibilitychange
Android は
visibilitychange が使えれば使う、（requestAnimationFrameも保険として登録）
visibilitychange が使えなければ blur で対応

Android 5.0.2 はどうしてもバックグラウンド検知ができない場合がある
（setTimeoutでアプリがバックグラウンドに回った際の処理の差で検知する方法もあるけど、端末差があったのであまり信用できない。。）

```js
/**
 * visibilityChange判定
 */
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
  hidden = "hidden";
  visibilityChange = "visibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
  hidden = "webkitHidden";
  visibilityChange = "webkitvisibilitychange";
}

/**
 * バックグランド遷移判定に何を使うか
 */
var available;
if(window.navigator.userAgent.toLowerCase().indexOf('iphone os 7_0') > -1){
  // iOS7はonpagehide固定
  available = 'onpagehide';
} else if(typeof document[hidden] === "undefined"){
  if(window.requestAnimationFrame){
    available = 'requestAnimationFrame';
  } else {
    available = 'blur';
  }
} else {
  available = 'visibilityChange';
}
```

```js
// 再生中にアプリがバックグラウンドにまわったか監視する
if(available === 'onpagehide'){
  window.onpagehide = function() {
    stop();
  };

} else if(available === 'blur'){
  window.addEventListener('blur', stop, false);

} else {
  observe();

  if(available === 'visibilityChange'){
    document.addEventListener(visibilityChange, stop, false);
  }
}
```

```js
/*
 * requestAnimationFrameで監視
 */
var raf_id, raf_timer;
function observe(){
  raf_id = window.requestAnimationFrame(observe);
  if(raf_timer !== false){
    clearTimeout(raf_timer);
  }
  raf_timer = setTimeout(function() {
    stop();
  }, 500);
};
```

```js
// バックグランド監視の停止
if(available === 'onpagehide'){
  window.onpagehide = null;
} else if(available === 'blur'){
  window.removeEventListener('blur', stop, false);
} else {

  // requestAnimationFrameの終了
  window.cancelAnimationFrame(raf_id);

  if(available === 'visibilityChange'){
    document.removeEventListener('visibilitychange', stop, false);
  }
}
```