Signatur
Beschreibung
Das Ereignis visibilitychange gehört zur Page Visibility API. Der Browser löst es am document aus, sobald sich document.visibilityState zwischen "visible" und "hidden" ändert. Das passiert etwa, wenn der Benutzer den Tab wechselt, das Browserfenster minimiert, den Bildschirm sperrt, auf dem Handy zu einer anderen App wechselt oder die Seite verlässt bzw. schließt.
Einen Handler registriert man mit document.addEventListener("visibilitychange", handler) oder über die Eigenschaft document.onvisibilitychange. Das Ereignis ist ein einfaches Event ohne Zusatzdaten, bubbelt nicht und lässt sich nicht abbrechen. Den aktuellen Zustand liest man im Handler über document.visibilityState bzw. document.hidden aus.
Typische Einsatzzwecke: Videos oder Animationen anhalten, Polling und Timer drosseln, ungespeicherte Daten sichern und Analyse- oder Sitzungsdaten zuverlässig senden. Der Wechsel nach "hidden" ist auf Mobilgeräten oft das letzte Ereignis, das eine Seite zuverlässig erhält. Deshalb gilt er als empfohlener Ersatz für unload und beforeunload, wenn es ums Speichern oder Senden von Daten geht.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignisname "visibilitychange". |
|
| $handler Pflicht | (event: Event) => void | Callback, der bei jeder Änderung der Sichtbarkeit aufgerufen wird. Er bekommt ein einfaches Event-Objekt; den Zustand liest man aus document.visibilityState. |
|
| $options | boolean | AddEventListenerOptions | false | Optionen wie once, passive, capture oder signal (zum Entfernen per AbortController). |
Rückgabewert
addEventListener hat keinen Rückgabewert. Der Handler erhält ein Event, das nicht bubbelt und nicht abbrechbar ist (cancelable: false).Beispiele
Video beim Tab-Wechsel pausieren (addEventListener)
const video = document.querySelector('video');
const onVisibilityChange = () => {
if (document.visibilityState === 'hidden') {
video.pause();
} else if (video.dataset.autoResume === 'true') {
video.play();
}
console.log('Sichtbarkeit:', document.visibilityState);
};
document.addEventListener('visibilitychange', onVisibilityChange);
Analysedaten zuverlässig beim Verlassen senden
const sessionStart = performance.now();
document.addEventListener('visibilitychange', () => {
if (document.visibilityState !== 'hidden') return;
const payload = JSON.stringify({
page: location.pathname,
duration: Math.round(performance.now() - sessionStart)
});
// sendBeacon übersteht das Schließen der Seite
navigator.sendBeacon('/api/analytics', payload);
});
Polling pausieren und Listener wieder entfernen
let timerId = null;
const poll = async () => {
const res = await fetch('/api/notifications');
console.log('Neue Meldungen:', (await res.json()).length);
};
const start = () => { timerId ??= setInterval(poll, 10_000); };
const stop = () => { clearInterval(timerId); timerId = null; };
const handler = () => (document.hidden ? stop() : start());
document.addEventListener('visibilitychange', handler);
start();
// Später, z. B. beim Abbau einer Komponente:
const cleanup = () => {
document.removeEventListener('visibilitychange', handler);
stop();
};
Variante über die on-Property
document.onvisibilitychange = () => {
document.title = document.hidden ? '💤 Komm zurück!' : 'Meine App';
};
// Entfernen:
// document.onvisibilitychange = null;
// Wichtig · Fallstricke
- Am
documentregistrieren, nicht amwindow: Das Ereignis wird amdocumentausgelöst und bubbelt nicht.window.addEventListener('visibilitychange', …)funktioniert zwar in manchen Browsern, weil Listener mitcaptureaufwindowes auf dem Weg abfangen, verlassen sollte man sich darauf aber nicht. - Ersatz für
unload/beforeunload: Auf Mobilgeräten werdenunload-Ereignisse oft gar nicht ausgelöst, etwa wenn der Benutzer die App wechselt und das System den Tab später beendet. Daten sichert man daher am besten beim Wechsel nach"hidden", am besten mitnavigator.sendBeacon()oderfetch(…, { keepalive: true }). Unload-Handler verhindern außerdem den Back/Forward-Cache. - Kein garantiertes Paar: Nach
"hidden"folgt nicht unbedingt noch ein"visible", denn die Seite kann danach ohne weitere Ereignisse beendet werden. Auch beim ersten Laden wirdvisibilitychangenicht ausgelöst. Den Anfangszustand prüft man deshalb direkt überdocument.visibilityState. - Nicht gleich Fokus: Eine Seite kann sichtbar sein, ohne den Fokus zu haben, z. B. in einem zweiten Fenster daneben. Für den Fokus sind
focus/blurzuständig. Ein teilweise verdecktes Fenster gilt meist weiterhin als"visible". - Veralteter Wert
"prerender": Frühere Fassungen der Spezifikation kannten den Zustand"prerender". Er ist entfernt worden. Heute gibt es nur noch"visible"und"hidden". Präfixversionen (webkitvisibilitychange) braucht man nur für sehr alte Browser. - Performance: In verborgenen Tabs drosseln Browser Timer ohnehin stark, und
requestAnimationFrameläuft dort gar nicht. Der Handler sollte schnell arbeiten, weil nach"hidden"möglicherweise nur noch wenig Rechenzeit bleibt.