Lorsqu'il s'agit d'aborder les performances des navigateurs web, de nombreux sujets existent : optimisation JavaScript, diminution des ressources à télécharger, meilleure écriture des sélecteurs CSS. Une autre fonctionnalité existe pour soulager le processeur : savoir si un document HTML est affiché ou non. Dans ce dernier cas, il sera possible de mettre en pause les traitements qui sont invisibles à l'utilisateurs et ne sont dès lors plus utiles, par exemple :
- mettre en pause une vidéo ou l'audio
- mettre en pause une animation Canvas, WebGL ou SVG
- ne pas effectuer de requêtes de type AJAX en arrière-plan pour rafraîchir les données
- ne pas afficher de notifications
L'API Page Visibility renseigne sur la visibilité d'une page, qu'elle occupe une fenêtre à elle toute seule ou juste un onglet. Dès qu'il y a changement d'état, un événement JavaScript est déclenché.
Notez que tout ceci n'a absolument rien à voir avec les propriétés CSS display
et visibility
puisqu'il s'agit d'un état global du document et non d'un élément HTML en particulier.
La théorie
Cette fonctionnalité est construite autour de l'événement visibilitychange
, qui va être déclenché par le navigateur lorsqu'il considérera que son contenu n'est pas « visible » par l'utilisateur :
- Si la fenêtre est réduite (minimisée)
- Si l'onglet n'est pas actif (l'utilisateur consulte une autre page web dans un autre onglet si le navigateur le permet)
Deux propriétés de l'interface document
seront disponibles pour surveiller son état.
La première est document.hidden
qui est un booléen :
-
true
(caché) -
false
(affiché)
La deuxième est document.visibilityState
qui correspond à plusieurs états :
-
hidden
: pas du tout visible, -
visible
: affiché totalement ou en partie, -
prerender
: chargement hors-écran non visible, -
unloaded
: en cours d'être déchargé de la mémoire
L'état prerender
correspond au pré-chargement du document par le navigateur et à son pré-calcul (rendu graphique). C'est une technique de plus en plus répandue et inaugurée par Chrome pour anticiper la navigation de l'utilisateur. En mode prerender, le document va être chargé de manière invisible en arrière-plan, le code JavaScript sera exécuté de manière classique. Quel est l'intérêt de faire cette distinction ? On pourra différer le chargement des outils de statistiques visiteurs, puisqu'il ne s'agira pas d'une consultation réelle mais d'une anticipation par le navigateur. Ceux-ci pourront reprendre leur exécution normale lorsqu'on passera en mode visible.
Tableau des compatibilités
Navigateurs | Versions |
---|---|
Internet Explorer 10 avec préfixe -ms- |
|
Firefox 10 avec préfixe |
|
Chrome 14 avec préfixe -webkit- Chrome 33 sans préfixe -webkit- Chrome Mobile 18 avec préfixe -webkit- |
|
Opera 12.1 Opera Mobile 12.1 |
|
Safari pas encore début 2013 |
|
Android Browser pas encore au début 2013 | |
BlackBerry 10 avec préfixe -webkit- |
La pratique
Pour mettre en place une surveillance de la visibilité du document, il faudra associer l'événement visibilitychange
à une fonction (ci-après nommée changementVisibilite
) qui va traiter le résultat.
Détection du changement de visibilité
document.addEventListener('visibilitychange', changementVisibilite, false);
function changementVisibilite() {
// On examine les propriétés :
// document.hidden
// document.visibilityState
// ...
}
Cet exemple minimal (et idéal) ne tient pas compte de la réalité des préfixes navigateurs, qui dans les premières versions de prise en charge, sont dotés de -moz-
(Mozilla Firefox), -ms-
(Microsoft Internet Explorer), -webkit-
(Chrome/Safari/autres). L'exemple ci-après est plus complet.
Détection du changement de visibilité avec préfixes navigateurs
<html>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Visibilité ?</title>
<p id="log"></p>
</head>
<body>
<script>
// Détection préalable des préfixes pour chaque moteur
// et stockage de leur nom dans des variables
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
visibilityState = "visibilityState";
} else if (typeof document.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
visibilityState = "mozVisibilityState";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
visibilityState = "msVisibilityState";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
visibilityState = "webkitVisibilityState";
}
// La variable hidden contient le nom de la propriété du document
// La variable visibilityChange contient le nom de l'événement
// La variable visibilityState contient la propriété d'état
document.addEventListener(visibilityChange, changementVisibilite, false);
// Elément qui va permettre un affichage informatif
var log = document.getElementById('log');
// Fonction qui traite l'événement
function changementVisibilite() {
log.innerHTML += 'Le document est caché ? '+document[hidden]+'<br>';
log.innerHTML += 'Etat : '+document[visibilityState]+'<br>';
}
</script>
</body>
</html>
Oui, cela fait tout de suite beaucoup plus de code, c'est contraignant et confus. Si vous souhaitez expérimenter la chose sur toutes les plate-formes par anticipation, il n'y a pas possibilité d'y échapper avant un abandon généralisé des préfixes pour cette fonctionnalité.
Le passage d'un onglet à l'autre, ou la réduction de la fenêtre provoque un aller-retour entre document caché et document visible (état hidden
puis visible
).
Dans la démonstration suivante, ce petit test est mené pour afficher des messages texte relatifs à la visibilité. Le reste du script reste le même.
Dans la dernière démonstration, il s'agit de modifier le comportement d'une animation Canvas : lorsque celle-ci détecte l'invisibilité, elle peut ralentir (ou se mettre en pause) et changer son processus. Pour cet exemple, l'avancement change de couleur (en vert lorsque le document est affiché, en rouge sinon, avec un défilement plus lent).
Globalement, le test reste toujours le même : se renseigner sur l'état et prendre des mesures en conséquence. Par exemple pour arrêter la lecture d'un élément <video id="mavideo">
:
if(document[hidden]) {
document.getElementById('mavideo').pause();
} else {
document.getElementById('mavideo').play();
}
Il ne reste plus qu'à appliquer ce principe à toutes les actions qui peuvent en profiter (voir les exemples de cas pratiques mentionnés en introduction).
Commentaires
Super API ! Merci pour l'article et les démos.
Merci !
Mais j'ai une question, qu'est-ce que cela ajoute par rapport à un window.onblur et window.onfocus ?
Les événements window.on* sont gérés au niveau de la fenêtre et ne sont pas déclenchés dans tous les cas appropriés, par exemple au niveau de la réduction de la fenêtre dans la barre des tâches (mais ça dépend bien sur du navigateur et de l'OS, ô joie). Par contre ils peuvent représenter une (petite) solution de secours pour les anciens moteurs ne comprenant pas l'API Page Visibility. Merci pour cette suggestion.
Bizarre, sur la démonstration 3, je n'ai que des petites barres rouges quand je revient sur la page (même après plusieurs seconds, je devrais donc avoir une longue barre rouge). J'utilise Chrome 24.0.1312.52 sur Mac OS 10.8.
Chrome dispose de ses propres optimisations complémentaires.
Si j'étais une quiche Lorraine je demanderais "et si javascript est désactivé" mais heureusement ce n'est pas le cas donc MERCI pour cet article.