Signatur
Beschreibung
Das Ereignis popstate wird am Window-Objekt ausgelöst, wenn sich der aktive Eintrag im Sitzungsverlauf ändert, während der Benutzer durch den Verlauf navigiert. Das passiert z. B., wenn er im Browser auf „Zurück“ oder „Vor“ klickt oder wenn Skripte history.back(), history.forward() oder history.go() aufrufen. Den Handler registrierst du mit window.addEventListener("popstate", handler) oder über die Eigenschaft window.onpopstate.
Wichtig für Single-Page-Anwendungen (SPA): popstate ist das Gegenstück zu history.pushState() und history.replaceState(). Mit diesen Methoden legst du Verlaufseinträge an oder änderst sie, ohne die Seite neu zu laden. Über popstate reagierst du dann auf die Navigation des Benutzers und stellst die passende Ansicht wieder her. Das Ereignisobjekt ist ein PopStateEvent. Seine Eigenschaft state enthält eine Kopie des Zustandsobjekts, das beim Anlegen des Eintrags übergeben wurde.
Achtung: Wenn du pushState() oder replaceState() selbst aufrufst, wird kein popstate ausgelöst. Das Ereignis entsteht nur durch eine Navigation im Verlauf. Wechselt der Benutzer zwischen zwei Einträgen desselben Dokuments, die sich nur im Fragment (#hash) unterscheiden, feuert popstate ebenfalls, und zwar vor hashchange.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignisname "popstate". |
|
| $handler Pflicht | (event: PopStateEvent) => void | Callback, der ein PopStateEvent erhält. Die wichtigste Eigenschaft ist event.state: das Zustandsobjekt des Verlaufseintrags, zu dem navigiert wurde, oder null. |
|
| $options | boolean | AddEventListenerOptions | false | Optionale Listener-Optionen wie once, passive oder signal (zum Entfernen per AbortController). |
Rückgabewert
addEventListener gibt nichts zurück. Der Handler bekommt ein PopStateEvent mit den Eigenschaften state (any) und hasUAVisualTransition (boolean, noch nicht in allen Browsern verfügbar).Beispiele
Einfaches SPA-Routing mit pushState und popstate
const render = (page) => {
document.querySelector('#app').textContent = `Seite: ${page}`;
};
const navigate = (page) => {
history.pushState({ page }, '', `/${page}`);
render(page); // pushState löst kein popstate aus – selbst rendern
};
window.addEventListener('popstate', (event) => {
// event.state ist null beim ursprünglichen Eintrag ohne Zustand
const page = event.state?.page ?? 'start';
console.log('Zurück/Vor zu:', page);
render(page);
});
navigate('produkte');
navigate('kontakt');
history.back(); // löst popstate asynchron aus
Anfangszustand mit replaceState setzen
// Damit auch der erste Eintrag einen Zustand hat
history.replaceState({ page: 'start', scrollY: 0 }, '');
window.onpopstate = ({ state }) => {
console.log('Zustand:', JSON.stringify(state));
if (state) window.scrollTo(0, state.scrollY);
};
history.pushState({ page: 'liste', scrollY: 400 }, '', '?liste');
history.back();
Listener per AbortController wieder entfernen
const controller = new AbortController();
const onPop = (event) => {
console.log('Filter wiederherstellen:', event.state?.filter);
};
window.addEventListener('popstate', onPop, { signal: controller.signal });
// Später, z. B. beim Unmount einer Komponente:
controller.abort();
// Alternativ klassisch:
// window.removeEventListener('popstate', onPop);
Reihenfolge von popstate und hashchange
window.addEventListener('popstate', () => console.log('popstate'));
window.addEventListener('hashchange', () => console.log('hashchange'));
location.hash = '#abschnitt-2';
// Danach im Browser auf „Zurück“ klicken
// Wichtig · Fallstricke
- Kein Auslösen durch
pushState/replaceState: Nach eigenen Aufrufen musst du die Ansicht selbst aktualisieren. statekannnullsein: Das gilt z. B. für den ersten Eintrag beim Seitenaufruf oder für Einträge, die über Hash-Links entstanden sind. Greif deshalb defensiv zu, etwa mitevent.state?.x.- Zustand wird geklont: Das Zustandsobjekt wird mit dem Structured-Clone-Algorithmus gespeichert. Funktionen, DOM-Knoten und Ähnliches sind nicht erlaubt und führen zu einer
DataCloneError-Ausnahme. Manche Browser begrenzen außerdem die Größe (Firefox z. B. auf etwa 16 MiB). - Beim Laden der Seite: Ältere Chrome- (vor 34) und Safari-Versionen (vor 10) haben
popstatebeim ersten Laden ausgelöst. Moderne Browser tun das nicht mehr. - Asynchron:
history.back()undhistory.go()arbeiten asynchron. Das Ereignis feuert also nicht sofort nach dem Aufruf. - Dokumentwechsel: Führt die Navigation zu einem anderen Dokument, feuert
popstateim neuen Dokument nicht. Reagiere dort stattdessen aufpageshow, auch wenn die Seite aus dem Back/Forward-Cache kommt. - Sicherheit:
locationundstatekönnen vom Benutzer manipuliert werden. Füge Werte daraus nie ungeprüft perinnerHTMLein (Gefahr von XSS). - Ausblick: Die neuere Navigation API (
navigation.addEventListener('navigate', …)) bietet ein umfassenderes Modell, wird aber noch nicht in allen Browsern unterstützt.