Podstawy#
Widoczność strony#
Dzięki API widoczności strony # deweloperzy mogą odczytywać aktualny stan widoczności strony na ekranie urządzenia wprost ze skryptu. Całość opisywana jest w pojedynczej specyfikacji W3C, która uzyskała już status rekomendacji:
W drugim wydaniu specyfikacji poprawiono jeden błąd typograficzny w sekcji 4.3.
Na dzień dzisiejszy wszystkie aktualne przeglądarki internetowej obsługują API widoczności strony bez żadnych problemów (w IE od wersji 10).
Przeznaczenie i korzyści#
Bez znajomości aktualnego stanu widoczności strony internetowej na ekranie urządzenia deweloperzy projektowali strony tak, jak gdyby były zawsze widoczne dla użytkownika. Powoduje to nie tylko większe wykorzystanie zasobów maszyny, ale i ograniczenia przy podejmowaniu decyzji przez deweloperów w oparciu o to, czy strona jest aktualnie widoczna dla użytkownika. Projektowanie stron internetowych ze znajomością widoczności strony pozwoli na poprawę doświadczenia użytkownika (user experience) a także zaoszczędzi nieco zasobów maszyny (ze szczególnym uwzględnieniem urządzeń mobilnych).
Nowoczesne przeglądarki internetowe mają natywne mechanizmy odzyskujące część zasobów pochłanianych przez niewidoczne strony, ale samodzielna detekcja widoczności strony zezwala na większą precyzję w tym obszarze.
W przeszłości deweloperzy zmuszeni byli do stosowania różnych półśrodków aby zapewnić choćby minimalne rozwiązanie problemu. Dla przykładu, rejestrowanie uchwytu na obiektach Window
dla zdarzenia typu blur
lub focus
mogło pomóc stwierdzić, kiedy strona przestała być aktywna, ale dalej nie mówiło nic na temat ukrycia strony przez użytkownika. Podstawowa różnica między zdarzeniami zogniskowania a omawianym API jest taka, że dla nowych poleceń strona nie traci widoczności, gdy inne okno staje się aktywne a okno przeglądarki traci zogniskowanie. Strona staje się niewidoczna tylko wtedy, gdy użytkownika przechodzi do innej zakładki lub minimalizuje okno przeglądarki.
Z nowymi poleceniami aplikacje internetowe mogą wybierać alternatywne zachowania na podstawie tego, czy są one widoczne dla użytkownika, czy nie. Dla przykładu, jeśli klient poczty internetowej jest widoczny, to może sprawdzać nowe wiadomości co kilka sekund, ale kiedy zostanie ukryty to może przejść w tryb sprawdzania co kilka minut. Innym równie ciekawym przykładem zastosowania będzie zatrzymywanie skomplikowanych operacji, które ze względu na brak widoczności strony stają się zbędne (np. automatyczna pauza podczas rozgrywki lub w czasie odtwarzania multimediów). Ponadto, polecenia te mogą być wykorzystywane przez reklamodawców, aby nie pobierać opłaty za reklamy, które nie są widoczne dla użytkowników.
Funkcja wstępnego renderowania (prerendering) wprowadzona w Chrome 13 powoduje, że pojawiają się nowe przypadki, w których detekcja widoczności strony może być szczególnie istotna.
Poniższy przykład obrazuje teoretycznego webowego klienta pocztowego, gdzie bez znajomości widoczności strony sprawdzanie nowych wiadomości odbywa się co sekundę:
<!DOCTYPE html>
<html>
<head>
<script>
var timer = 0;
var PERIOD = 1000; // 1 sekunda
function onLoad(){
timer = setInterval(checkEmail, PERIOD);
}
function checkEmail() {
debugMessage("Sprawdzenie wiadomości o " + new Date().toTimeString());
}
function debugMessage(s) {
var p = document.createElement("p");
p.appendChild(document.createTextNode(s));
document.body.appendChild(p);
}
</script>
</head>
<body onload="onLoad()">
</body>
</html>
Skrypt zawsze sprawdza nowe wiadomości co każdą sekundę, nawet jeśli użytkownik aktualnie nie przegląda strony ze względu na jej niewidoczność. Jest to przykład wyjątkowo słabego zarządzania zasobami.
Przy użyciu właściwości Document.hidden
lub Document.visibilityState
i dedykowanego zdarzenia visibilitychange
można pokusić się o sprawdzanie nowych wiadomości nieco rzadziej, kiedy strona nie jest aktualnie widoczna:
<!DOCTYPE html>
<html>
<head>
<script>
var timer = 0;
var PERIOD_VISIBLE = 1000; // 1 sekunda
var PERIOD_NOT_VISIBLE = 10000; // 10 sekund
function onLoad(){
timer = setInterval(checkEmail, (document.hidden) ? PERIOD_NOT_VISIBLE : PERIOD_VISIBLE);
if(document.addEventListener) document.addEventListener("visibilitychange", visibilityChanged);
}
function visibilityChanged(){
clearTimeout(timer);
timer = setInterval(checkEmail, (document.hidden) ? PERIOD_NOT_VISIBLE : PERIOD_VISIBLE);
debugMessage("Przejście na " + (document.hidden ? "nie" : "") + "widoczny o " + new Date().toTimeString());
}
function checkEmail() {
debugMessage("Sprawdzenie wiadomości na " + (document.hidden ? "nie" : "") + "widocznym o " + new Date().toTimeString());
}
function debugMessage(s) {
var p = document.createElement("p");
p.appendChild(document.createTextNode(s));
document.body.appendChild(p);
}
</script>
</head>
<body onload="onLoad()">
</body>
</html>
Oczywiście istnieje wiele innych obszarów zastosowania, gdzie polecenia związane z detekcją widoczności strony mogą być niezwykle użyteczne. Oto kilka z nich:
- Automatyczne zatrzymywanie i wznawianie pokazu slajdów (lub innych multimediów i "ciężkich" animacji).
- Automatyczne zatrzymywanie i wznawianie (lub spowolnienie) odpytywania serwera dla różnych celów (np. wyświetlania aktualnych informacji w panelu).
- Wykrycie wstępnego renderowania strony dla dokładniejszego oszacowania liczby odsłon witryny.
- Zliczanie czasu obecności użytkownika na stronie tylko wtedy, kiedy strona jest widoczna.
- Wyświetlanie powiadomień tylko wtedy, kiedy strona jest niewidoczna.
Obserwowanie zmiany widoczności strony#
Z widocznością strony związanych jest kilka kluczowych pojęć i algorytmów. Dokładna analiza materiału jest jak najbardziej wskazana, ale dla osób mniej obytych z algorytmami opiszę podstawy całego mechanizmu.
Aktualne przeglądarki internetowe obserwują stan widoczność strony, i jeśli ulegnie on zmianie, to wysyłają dedykowane zdarzenie typu visibilitychange
. Możemy przechwycić to zdarzenie poprzez zarejestrowanie nasłuchu zdarzenia bezpośrednio na obiekcie Document
.
Należy wyraźnie podkreślić, że widoczność nie ma nic wspólnego z tym, czy zawartość dokumentu została w pełni załadowana, co oznacza, że dla każdego zdarzenia reprezentującego zmianę widoczności zdarzenie typu load
mogło, ale wcale nie musiało zostać odpalone. Ponadto zdarzenie visibilitychange
wcale nie zostanie odpalone, jeśli stronę otworzono na pierwszym planie i nie została wstępnie zrenderowana.
Bieżący stan widoczności strony odczytamy bezpośrednio z właściwości Document.hidden
lub Document.visibilityState
, chociaż ta druga (przynajmniej w teorii) oferuje większe pole manewru. W oparciu o zwracane wartości możemy podjąć określone czynności, np. przerwać lub wznowić odtwarzanie filmu.
Poziom obsługi przytaczanych właściwości w obecnych przeglądarkach internetowych jest różny. Z podstawowym zachowaniem nie ma problemu, ale niektóre programy mogą kompletnie nie reagować na przeładowanie dokumentu, na prerendering czy na ekran blokady systemu operacyjnego (z uwzględnieniem wygaszacz ekranu). W zasadzie najpewniejsze pozostają jedynie działania związane z minimalizacją i przywracaniem okna przeglądarki oraz zmiana aktywnej karty.
Prosty przykład:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p style="color: blue;">Szczegółowe informacje dla przechwyconego zdarzenia:</p>
<p id="info"></p>
<script>
var info = document.getElementById("info");
info.innerHTML = "document.hidden (przed zmianą): " + document.hidden
+ "<br>" + "document.visibilityState (przed zmianą): " + document.visibilityState + "<br><br>";
document.title = document.hidden + "|" + document.visibilityState;
function readInfo(e){
var data = "Interfejs: " + e
+ "<br>" + "e.type: " + e.type
+ "<br>" + "e.currentTarget: " + e.currentTarget
+ "<br>" + "e.target: " + e.target
+ "<br>" + "e.bubbles: " + e.bubbles
+ "<br>" + "e.cancelable: " + e.cancelable
+ "<br>" + "e.isTrusted: " + e.isTrusted
+ "<br>" + "e.eventPhase: " + e.eventPhase
+ "<br>" + "document.hidden (po zmianie): " + document.hidden
+ "<br>" + "document.visibilityState (po zmianie): " + document.visibilityState + "<br><br>";
info.innerHTML = info.innerHTML + data;
document.title = document.hidden + "|" + document.visibilityState;
console.log(data);
}
document.addEventListener("visibilitychange", readInfo, false); // faza celu - trzeci argument nieistotny
window.addEventListener("visibilitychange", readInfo, true); // przechwyci zdarzenie dla dokumentu
</script>
</body>
</html>
Najlepiej po prostu poeksperymentować z następującym filmem (zawiera ścieżkę dźwiękową). Oto część użytego kodu (sam JS):
<script>
(function() {
// Set the name of the "hidden" property and the change event for visibility
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") { // Firefox up to v17
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") { // Chrome up to v32, Android up to v4.4, Blackberry up to v10
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
var videoElement = document.getElementById("videoElement");
// If the page is hidden, pause the video;
// if the page is shown, play the video
function handleVisibilityChange() {
if (document[hidden]) {
videoElement.pause();
} else {
videoElement.play();
}
}
// Warn if the browser doesn't support addEventListener or the Page Visibility API
if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
alert("This demo requires a modern browser that supports the Page Visibility API.");
} else {
// Handle page visibility change
document.addEventListener(visibilityChange, handleVisibilityChange, false);
// When the video pauses and plays, change the title.
videoElement.addEventListener("pause", function(){
document.title = 'Paused';
}, false);
videoElement.addEventListener("play", function(){
document.title = 'Playing'
}, false);
}
})();
</script>
Na sam koniec jeszcze jeden przykład z filmem, gdzie tytuł strony (przy użyciu właściwości Document.title
) prezentuje aktualny czas odtwarzania filmu z dodatkowymi ikonami odtwarzania i pauzy.