こんにちは。
「un-T factory! XA Advent Calendar 2022」 の2番バッターです。
しっかり送りバントを決めたいと思います。
よろしくおねがいします!
さて今回は、
Page Visibility APIについて知っていきたいと思います。
皆さん、Page Visibility APIについてどのくらいご存じでしょうか?
- 動画プレーヤーの制御
- スライドショーの実装時の自動再生設定
- GPUを使用して描画を行う際の処理制御
などで、考えたことがある方は少なからずいらっしゃるかもしれません、
あるいは、Page Visibility APIを認識していなくても
プロパティDocument.visibilityState
やイベントDocument:visibilitychange
は
知っているぞ。という方もいらっしゃるかと思います。
本記事は上記についてまだしっかり意識したことがない、
主に初学者向けな内容になるかと思いますが、
進めていきます。
Page Visibility APIとは?
まーこうゆうのはmdn web docsを読んでおこうってことで
https://developer.mozilla.org/ja/docs/Web/API/Page_Visibility_API
タブを使って閲覧している場合、どのウェブページもバックグラウンドにあってユーザーから見えていない場合があります。 Page Visibility API では、現在ページが見えているかどうかを調べる機能とともに、文書が表示されたり非表示になったりした時を監視することができるイベントを提供します。
ユーザーがウィンドウを最小化したり他のタブに切り替えたりした時、 API はvisibilitychange
イベントを送信してリスナーにページの状態が変化したことを知らせます。イベントを検出していくつかの操作を実行したり、様々な動作をしたりすることができます。例えば、ウェブアプリで動画を再生している場合、ユーザーがタブをバックグラウンドにした場合に動画を一時停止させ、ユーザーがこのタブに戻ったときに再生を再開させたりすることができます。ユーザーは動画の位置に迷うことがなく、動画の音声が新しく前景になったタブの音声を邪魔せず、ユーザーがその間に動画を見落とすことがなくなります。
と書いてあります。
具体的な使用例としては、
- 画像のスライドショーがあるサイトで、ユーザーが見ていない間に次のスライドに進むべきではないもの
- 情報をダッシュボードに表示するアプリケーションで、ページが見えていないときは更新情報をサーバーへ問い合わせてほしくないもの
- 正確なページビューをカウントできるよう、ページがプリレンダリングされている状態を検出したい。
- デバイスがスタンバイモードである (ユーザーが電源ボタンを押して、画面を消灯している) ときに、音声を止めたいサイト
と記載されていますが、
先に書いたように、
- ユーザーが見ていない間は動画を一時停止したい
- GPUを使用した高負荷なアニメーションを実装する際に不必要なタスクの実行を抑止したい
など他にも利用できる局面は多いのかなと思ってます。
Page Visibility API とは別に、ユーザーエージェントは通常、バックグラウンドまたは非表示のタブのパフォーマンスへの影響を軽減するためのポリシーを数多く持っているようです。例えば、ほとんどのブラウザは、パフォーマンスとバッテリ寿命を向上させるために、バックグラウンド タブまたは非表示の iframe に requestAnimationFrame() コールバックを送信しないようにします。
実際の使用法
さて実際の使用方法についてですが
【プロパティ】
Document.hidden
もしくはDocument.visibilityState
と
【イベント】
Document.onvisibilitychange
を使用して実装していく形になるかと思います。
プロパティから見ていくと、
Document.hiddenとDocument.visibilityStateの違いについて、
Document.hiddenはページがユーザーから隠れた状態か否かを、
真偽値(true/false)で返します。
一方のDocument.visibilityStateは文書の現在の可視状態を示す、
DOMStringを返します。
返される値のパターンは以下の通りです。
visible
ページのコンテンツは少なくとも部分的に可視状態です。実際は、最小化されていないウィンドウのフォアグラウンドのタブにページがあることを意味します。
hidden
ページのコンテンツはユーザーから見えていません。実際は、文書がバックグラウンドのタブか最小化されているウィンドウにある、あるいは OS のスクリーンがロックされていることを意味します。
prerender
ページのコンテンツはプリレンダリングされており、ユーザーから見えていません (document.hidden
では隠されているとみなされます)。文書は prerender
の状態から始まるかもしれませんが、プリレンダリングは 1 つの文書は 1 回しか行われないので、他の状態からこの状態に移ることはありません。
すべてのブラウザーがプリレンダリングに対応しているわけではありません。
unloaded
ページがメモリからアンロードされている途中です。
すべてのブラウザーが unloaded の値に対応しているわけではありません。
上記の通り値にパターンがあり内いくつかの値はブラウザによってサポートされていないようなので、Document.visibilityStateほどの判定精度を要していない実装の場合は
Document.hiddenで良いのかなと思ってます。
続いてイベントを見ていきます。
といってもこちらはvisibilitychange イベントが発生したときに呼び出されるコードを提供する EventListener というだけです!
なのでvisibilitychange イベントで文章の表示/非表示状態の変化を感知し、
実際の表示/非表示状態はDocument.hiddenもしくはDocument.visibilityStateで、
取得するという流れになります。
※実装例
//backgroundMusic は別途定義される
//Document.hiddenを使用する場合
document.addEventListener("visibilitychange", function() {
// Opera 12.10 や Firefox 18 以前の対策をする場合
// 考慮しないのであればdocument.hiddenのみで良い
if (document.hidden || document.mozHidden || document.webkitHidden || document.msHidden) {
//非表示状態なら停止
backgroundMusic.pause();
} else {
//表示状態なら再生
backgroundMusic.play();
}
});
//Document.visibilityStateを使用する場合
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === 'visible') {
//表示状態なら再生
backgroundMusic.play();
} else {
//非表示状態なら停止
backgroundMusic.pause();
}
});
ちなみにvisibilitychange イベントはW3C Recommendation には
ユーザーエージェントは、トップレベルのブラウジングコンテキストが含むドキュメントの可視性が変化したと判断したとき、ドキュメントで visibilitychange イベントを発生させなければならない。(DeepL訳)
と記載されています。
親となるブラウジング・コンテキストがないブラウジング・コンテキストはそのブラウジング・コンテキスト自身とその子孫ブラウジング・コンテキストの“最上位のブラウジング・コンテキスト(top-level browsing context)”と言います。通常ブラウザのウインドウもしくはタブが直接含むブラウジング・コンテキストが最上位のブラウジング・コンテキストです。
onfocus / onblur イベントとの違い
ここまで聞いてイヤイヤ、ちょっと待ってくれよ。
onfocus / onblur でいいじゃないか。と思った方のために
違いを記載しておきます。
といってもWindow: focus
イベントと Window: blur
イベントは
その名の通りフォーカス状態を表すものなので、
表示/非表示状態を表すものではありません。
一応CodePenにそれぞれの状態がわかるものを用意しました。
See the Pen three.js 22-2 by Keiichiro Shigematsu (@Shigematsu) on CodePen.
都合が良いのか悪いのか、エディター部分とプレビュー部分それぞれがiframeなので
onfocus / onblur イベントはエディター部分とプレビュー部分でフォーカスを切り替えるだけで発火します。(windowオブジェクトのイベントですしね。。)
フォーカスしたり、画面を最小化したり、タブを切り替えたりして、挙動を確認してみてください。
それと比較して、Document.onvisibilitychange イベントはページの表示/非表示情報を感知するので
最小化やタブの切り替えをしないと発火しません。
ちなみにページ非表示状態は"非表示"という書き込みがなされるはずですが、当然確認できません。
(念の為console.log記述もつっこんでいるので検証ツールで発火の確認はできます。)
他にも下記のような違いがあるようです。
【Document.onvisibilitychange イベント】
- Mac で、同じデスクトップ内の別アプリケーションに切り替えても発火しない
- スマホで別アプリやホーム画面への行き来で発火する
【onfocus / onblur イベント】
- Mac の同じデスクトップ内でも、別アプリケーションに移動すれば blur が発火する
- スマホで別アプリやホーム画面から戻ってくると focus が発火しない
組み合わせて実装する、ということも考えられますが、
CodePenで分かるようにonfocus / onblur イベントはwindow オブジェクトのイベントですので、windowが複数存在するページでの制御には向いてない気がしてます。
まとめ
実装例を見ていただければ分かるように、実装はすごくシンプルで、
すぐにでも取り入れられるかと思います。
なので、
ユーザー体験の向上やページ表示中のパフォーマンス管理など
ちょっと意識を持ってガシガシ使っていきましょう〜
それでは皆さん、風邪など引かれぬよう体調管理をして
良き年末を過ごしましょう〜
参考サイト
https://developer.mozilla.org/ja/docs/Web/API/Page_Visibility_API
https://developer.mozilla.org/ja/docs/Web/API/Document/visibilitychange_event
https://www.w3.org/TR/page-visibility/#sec-visibilitychange-event
https://web.havincoffee.com/html/html5/html5_a_browsingcontext.html
https://zenn.dev/raihara3/articles/20220214_background_tab