• Page Visibility APIではdocument.hiddenで非表示を判定し、visibilitychangeで処理を止めて戻す

Page Visibility APIではdocument.hiddenで非表示を判定し、visibilitychangeで処理を止めて戻す

公開日: 執筆:佐藤

画面に見えていないページで、表示更新やserverへの定期確認を同じ間隔のまま続ける必要はありません。Page Visibility APIのdocument.hiddenで非表示を判定し、visibilitychangeを受けたときに処理を止め、表示へ戻ったら再開します。

大切なのは、eventを監視することではなく、停止と再開を同じ関数へまとめることです。setInterval()を再開するたびに増やすと、画面へ戻るたびに同じ処理が重複します。

document.hiddenは見えているかをbooleanで返す

document.hiddenは、top levelのbrowsing contextにある文書が、どの画面にもまったく見えていない場合にtrueを返します。少なくとも一部が一つの画面に見えていればfalseです。

たとえば、利用者が別tabへ移った場合や、browserを最小化した場合は、ページが非表示になる例です。ただし、document.hiddenが分かるのはページの可視状態です。利用者が文章を読んでいるか、席を離れているか、入力を続ける意思があるかまでは分かりません。

処理を止める判断にはbooleanのdocument.hiddenを使い、状態を詳しく調べたい場合にdocument.visibilityStateを使うと役割を分けられます。

visibilityStateは状態名を返す

document.visibilityStateは、visible、hidden、prerender、unloadedのいずれか、またはvendor prefix付きの状態名を返します。prerenderとunloadedの値はuser agentによって対応が任意です。

表示更新を動かすか止めるかだけなら、すべての状態名を列挙するより、document.hiddenで分ける方が単純です。記録や調査で「どの状態へ変わったか」を残す必要がある場合は、event handler内でvisibilityStateを読みます。

function logVisibility() {
    window.console.log( document.visibilityState );
}

document.addEventListener( 'visibilitychange', logVisibility, false );

この例は状態名を確認するだけです。処理の停止や再開は自動では行われません。

visibilitychangeで停止と再開を一度ずつ実行する

次は、ページが見えている間だけ30秒ごとに状態を更新する仮例です。実在siteの通信処理や実測値ではありません。

(function () {
    var timerId = null;

    function refreshStatus() {
        // 必要な表示更新またはserverへの確認を行う
    }

    function startUpdates() {
        if ( timerId !== null ) {
            return;
        }

        refreshStatus();
        timerId = window.setInterval( refreshStatus, 30000 );
    }

    function stopUpdates() {
        if ( timerId === null ) {
            return;
        }

        window.clearInterval( timerId );
        timerId = null;
    }

    function applyVisibility() {
        if ( document.hidden ) {
            stopUpdates();
        } else {
            startUpdates();
        }
    }

    if ( 'hidden' in document && document.addEventListener ) {
        document.addEventListener(
            'visibilitychange',
            applyVisibility,
            false
        );
        applyVisibility();
    } else {
        startUpdates();
    }
}());

timerIdがnullでない間は、新しいintervalを追加しません。停止時にはclearInterval()を呼び、再開できるようにnullへ戻します。最初にもapplyVisibility()を呼ぶため、読み込み時点ですでに非表示なら定期処理を始めません。

表示へ戻ったときは、intervalの次回実行を待たずにrefreshStatus()を一度呼びます。非表示の間に内容が変わる可能性がある画面では、この一回で表示を追い付きやすくできます。更新内容が重い場合は、即時実行が必要かを処理ごとに決めます。

eventだけでは進行中の通信や動画は止まらない

visibilitychangeが発生しても、すでに始まった通信、動画再生、animation、timerが一律に停止するわけではありません。何を止め、何を続けるかをhandlerへ書きます。

処理 非表示時の候補 表示へ戻ったときの候補
新着情報の定期確認 次回以降のtimerを止める 一度確認してからtimerを戻す
canvas animation 描画予約を止める 現在時刻から状態を計算し直す
video 目的に応じて停止を検討する 利用者の操作を尊重して再開を決める
入力内容 保存中の内容を維持する 勝手に初期化しない

非表示になっただけで、利用者の操作を無視して動画を自動再開したり、入力途中のformを消したりしてはいけません。機械の負荷を減らす目的と、利用者が決める操作を分けます。

対応していない環境では通常処理へ戻す

対応browserを名前だけで決めず、対象環境でdocument.hidden、document.visibilityState、visibilitychangeを確認します。上の仮例では'hidden' in documentを調べ、利用できない場合は通常の定期処理を続けます。

fallbackを用意しても、非対応環境で負荷を減らせるとは限りません。対応環境では停止でき、非対応環境では従来の動作を壊さない、という範囲です。対象browserと確認結果は、codeと一緒に運用記録へ残します。

最初の一手は、ページ内のtimerを全部書き換えることではありません。最も頻繁に動く処理を一つ選び、非表示時に止めても利用者の入力や保存を失わないかを確認してください。

戻る