logo oujood
🔍

Page Visibility API JavaScript

La Page Visibility API permet de savoir si l'onglet est actuellement visible par l'utilisateur ou en arrière-plan. Une information simple mais précieuse pour mettre en pause les animations, stopper les requêtes inutiles et économiser les ressources.

OUJOOD.COM

Un utilisateur a souvent plusieurs onglets ouverts en même temps. Quand votre page est en arrière-plan, continuer à jouer des animations, envoyer des requêtes toutes les secondes ou faire tourner des calculs coûteux est inutile — et pénalise la batterie et les performances globales de l'appareil.

La Page Visibility API résout ce problème avec deux propriétés et un événement. C'est l'une des APIs les plus légères de HTML5 : quelques lignes suffisent pour rendre une application nettement plus efficace.

Les deux propriétés essentielles

document.hidden retourne un booléen simple. document.visibilityState donne plus de détails avec une chaîne de caractères :

Exemple :   📋 Copier le code

// Vérifier si la page est cachée
console.log(document.hidden);
// true si l'onglet est en arrière-plan, false s'il est visible

// État détaillé de la visibilité
console.log(document.visibilityState);
// "visible"  — l'onglet est actif et visible
// "hidden"   — l'onglet est en arrière-plan ou la fenêtre est minimisée

Détecter les changements de visibilité

L'événement visibilitychange se déclenche à chaque fois que l'utilisateur change d'onglet, minimise la fenêtre, ou revient sur la page. C'est le cœur de l'API :

Exemple :   📋 Copier le code

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('Onglet en arrière-plan — mise en pause.');
  } else {
    console.log('Onglet redevenu visible — reprise.');
  }
});

Mettre en pause une vidéo ou une animation

Le cas d'usage le plus direct : mettre en pause automatiquement une vidéo quand l'utilisateur change d'onglet, et la reprendre à son retour :

Exemple :   📋 Copier le code

const video = document.getElementById('maVideo');

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    video.pause();
  } else {
    // Reprendre seulement si la vidéo était en lecture
    video.play();
  }
});

Suspendre un intervalle de polling

Beaucoup d'applications interrogent le serveur à intervalles réguliers pour récupérer des données fraîches. Continuer ce polling quand l'onglet est invisible gaspille la bande passante et les ressources serveur. La solution : suspendre l'intervalle et le relancer au retour :

Exemple :   📋 Copier le code

let intervalId = null;

function demarrerPolling() {
  if (intervalId) return; // déjà actif
  intervalId = setInterval(async () => {
    const data = await fetch('/api/notifications').then(r => r.json());
    mettreAJourInterface(data);
  }, 5000);
}

function arreterPolling() {
  clearInterval(intervalId);
  intervalId = null;
}

// Démarrer ou arrêter selon la visibilité
document.addEventListener('visibilitychange', () => {
  document.hidden ? arreterPolling() : demarrerPolling();
});

// Démarrer au chargement si l'onglet est déjà visible
if (!document.hidden) demarrerPolling();

Optimiser les animations avec requestAnimationFrame

Les animations basées sur requestAnimationFrame sont déjà ralenties automatiquement par le navigateur quand l'onglet est en arrière-plan, mais les arrêter complètement économise encore plus de ressources :

Exemple :   📋 Copier le code

let animationId = null;

function animer(timestamp) {
  // Logique d'animation ici
  dessinerFrame(timestamp);
  animationId = requestAnimationFrame(animer);
}

function demarrerAnimation() {
  if (!animationId) {
    animationId = requestAnimationFrame(animer);
  }
}

function arreterAnimation() {
  if (animationId) {
    cancelAnimationFrame(animationId);
    animationId = null;
  }
}

document.addEventListener('visibilitychange', () => {
  document.hidden ? arreterAnimation() : demarrerAnimation();
});

demarrerAnimation();

Exemple pratique : chronomètre précis

Un chronomètre basé sur setInterval dérive quand l'onglet est en arrière-plan car les intervals sont throttlés par le navigateur. La solution correcte : mesurer le temps réel écoulé avec Date.now(), et reprendre proprement à la visibilité :

Exemple :   📋 Copier le code

let debutTemps = null;
let tempsEcoule = 0;
let intervalChrono = null;

function demarrerChrono() {
  debutTemps = Date.now();
  intervalChrono = setInterval(() => {
    const maintenant = Date.now();
    tempsEcoule += maintenant - debutTemps;
    debutTemps = maintenant;
    afficherTemps(tempsEcoule);
  }, 100);
}

function arreterChrono() {
  clearInterval(intervalChrono);
  intervalChrono = null;
}

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    // Sauvegarder le temps au moment où l'onglet se cache
    tempsEcoule += Date.now() - debutTemps;
    arreterChrono();
  } else {
    // Reprendre depuis le temps sauvegardé
    demarrerChrono();
  }
});

Suivre le temps de présence réel

La Page Visibility API permet aussi de mesurer le temps qu'un utilisateur passe réellement sur une page — en ne comptant que les périodes où l'onglet est visible :

Exemple :   📋 Copier le code

let tempsPresence = 0;
let debutVisible = document.hidden ? null : Date.now();

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    // L'onglet se cache — accumuler le temps visible
    if (debutVisible) {
      tempsPresence += Date.now() - debutVisible;
      debutVisible = null;
    }
  } else {
    // L'onglet redevient visible — démarrer le chrono
    debutVisible = Date.now();
  }
});

// Envoyer le temps de présence au serveur avant de quitter
window.addEventListener('beforeunload', () => {
  if (debutVisible) tempsPresence += Date.now() - debutVisible;
  navigator.sendBeacon('/api/analytics', JSON.stringify({ tempsPresence }));
});

Compatibilité et cas particuliers

La Page Visibility API est disponible dans tous les navigateurs modernes sans préfixe. Quelques comportements à connaître :

  • Fenêtre minimisée : document.hidden passe à true quand la fenêtre du navigateur est minimisée, même si l'onglet est actif.
  • Écran verrouillé : sur mobile, verrouiller l'écran déclenche visibilitychange avec hidden = true.
  • Throttling des timers : les navigateurs ralentissent déjà setTimeout et setInterval quand une page est cachée. La Page Visibility API permet d'aller plus loin en les arrêtant complètement.

Par carabde | Mis à jour le 17 avril 2026


chapitre précédent   sommaire   chapitre suivant