Page Lifecycle API

Unterstützte Browser

  • 68
  • 79
  • x
  • x

Moderne Browser sperren Seiten manchmal aus oder verwerfen sie vollständig, wenn die Systemressourcen begrenzt sind. In Zukunft möchten Browser dies proaktiv tun, damit sie weniger Energie und Arbeitsspeicher verbrauchen. Die Page Lifecycle API bietet Lebenszyklus-Hooks, damit Ihre Seiten diese Browser-Eingriffe sicher verarbeiten können, ohne die Nutzererfahrung zu beeinträchtigen. Sehen Sie sich die API an und entscheiden Sie, ob Sie diese Funktionen in Ihrer Anwendung implementieren sollten.

Hintergrund

Der Anwendungslebenszyklus ist eine wichtige Möglichkeit für moderne Betriebssysteme, Ressourcen zu verwalten. Unter Android-, iOS- und neueren Windows-Versionen können Apps jederzeit über das Betriebssystem gestartet und beendet werden. Auf diese Weise können die Ressourcen auf diesen Plattformen optimiert und neu verteilt werden, wo es für die Nutzenden am besten nützt.

Im Internet gibt es in der Vergangenheit keinen solchen Lebenszyklus und Anwendungen können auf unbestimmte Zeit aktiv gehalten werden. Bei einer großen Anzahl von ausgeführten Webseiten kann es vorkommen, dass kritische Systemressourcen wie Arbeitsspeicher, CPU, Akku und Netzwerk überlastet sind, was zu einer schlechten Endnutzererfahrung führt.

Auf der Webplattform gibt es zwar schon lange Ereignisse im Zusammenhang mit Lebenszyklusstatus wie load, unload und visibilitychange. Mit diesen Ereignissen können Entwickler aber nur auf vom Nutzer initiierte Lebenszyklusstatusänderungen reagieren. Damit das Web auf Geräten mit geringem Stromverbrauch zuverlässig funktioniert (und im Allgemeinen auf allen Plattformen bewusster auf Ressourcen achtet), müssen Browser Systemressourcen proaktiv zurückgewinnen und neu zuweisen.

Tatsächlich ergreifen Browser bereits heute aktive Maßnahmen zur Schonung von Ressourcen für Seiten, die sich auf Tabs im Hintergrund befinden. Viele Browser (insbesondere Chrome) würden noch viel mehr tun, um ihren Ressourcenbedarf insgesamt zu verringern.

Das Problem ist, dass Entwickler keine Möglichkeit haben, sich auf diese Arten von vom System initiierten Eingriffen vorzubereiten oder überhaupt zu wissen, dass sie stattfinden. Das bedeutet, dass Browser konservativ sein müssen, da sonst die Gefahr besteht, dass Webseiten nicht ordnungsgemäß funktionieren.

Die Page Lifecycle API versucht, dieses Problem folgendermaßen zu lösen:

  • Einführung und Standardisierung des Konzepts von Lebenszyklusstatus im Web.
  • Definieren neuer, vom System initiierter Status, mit denen Browser die Ressourcen begrenzen können, die von ausgeblendeten oder inaktiven Tabs genutzt werden können.
  • Erstellen neuer APIs und Ereignisse, mit denen Webentwickler auf Übergänge zu und von diesen neuen vom System initiierten Zuständen reagieren können.

Diese Lösung bietet die Vorhersehbarkeit, die Webentwickler benötigen, um Anwendungen zu erstellen, die Systeminterventionen standhalten. Außerdem ermöglicht sie Browsern, Systemressourcen aggressiver zu optimieren, was letztendlich allen Webnutzern zugutekommt.

Im weiteren Verlauf dieses Beitrags werden die neuen Funktionen des Seitenlebenszyklus vorgestellt und ihre Beziehung zu den bestehenden Status und Ereignissen der Webplattform erläutert. Außerdem finden Sie Empfehlungen und Best Practices für die Art von Arbeit, die Entwickler in jedem Status ausführen sollten (und nicht).

Übersicht über Status und Ereignisse im Seitenlebenszyklus

Alle Seitenlebenszyklus-Zustände sind diskret und schließen sich gegenseitig aus, d. h., eine Seite kann jeweils nur einen Status haben. Die meisten Änderungen am Lebenszyklusstatus einer Seite lassen sich in der Regel über DOM-Ereignisse beobachten. Ausnahmen finden Sie in den Empfehlungen für Entwickler für die einzelnen Status.

Am einfachsten lassen sich die Zustände des Seitenlebenszyklus sowie die Ereignisse, die Übergänge zwischen ihnen signalisieren, anhand eines Diagramms erklären:

Eine visuelle Darstellung des Status- und Ereignisflusses, der in diesem Dokument beschrieben wird.
Status und Ereignisablauf der Page Lifecycle API.

Bundesstaaten

In der folgenden Tabelle werden die einzelnen Bundesstaaten im Detail erläutert. Außerdem werden die möglichen Status davor und danach aufgeführt sowie die Ereignisse, mit denen Entwickler Änderungen beobachten können.

Status Beschreibung
Aktiv

Eine Seite hat den Status Aktiv, wenn sie sichtbar ist und Eingabefokus hat.

Mögliche vorherige Status:
Passiv (über das Ereignis focus)
Eingefroren (über das Ereignis resume, dann das Ereignis pageshow)

Mögliche nächste Status:
Passiv (über das Ereignis blur)

Passiv

Eine Seite hat den Status Passiv, wenn sie sichtbar ist und keinen Eingabefokus hat.

Mögliche vorherige Status:
aktiv (über das Ereignis blur)
ausgeblendet (über das Ereignis visibilitychange)
eingefroren (über das Ereignis resume, dann das Ereignis pageshow{/22)

Mögliche nächste Status:
aktiv (über das Ereignis focus)
ausgeblendet (über das Ereignis visibilitychange)

Ausgeblendet

Eine Seite hat den Status Ausgeblendet, wenn sie nicht sichtbar ist und nicht eingefroren, verworfen oder beendet wurde.

Mögliche vorherige Status:
Passiv (über das Ereignis visibilitychange)
Eingefroren (über das Ereignis resume, dann das Ereignis pageshow)

Mögliche nächste Status:
Passiv (über visibilitychange-Ereignis)
eingefroren (über freeze-Ereignis)
verworfen (keine Ereignisse ausgelöst)
beendet (keine Ereignisse ausgelöst)

Eingefroren

Im Status Eingefroren unterbricht der Browser die Ausführung von fixierbaren Aufgaben in den Aufgabenwarteschlangen der Seite, bis die Seite nicht mehr fixiert ist. Das bedeutet, dass beispielsweise JavaScript-Timer und Abruf-Callbacks nicht ausgeführt werden. Bereits ausgeführte Aufgaben können beendet werden (am wichtigsten der freeze-Callback), aber unter Umständen gibt es Einschränkungen in Bezug auf die Aktionen und die Dauer der Ausführung.

Browser fixieren Seiten, um die CPU-/Akku-/Datennutzung zu schonen. Außerdem ermöglichen sie so eine schnellere Zurück- und Vorwärtsnavigation, sodass die ganze Seite nicht neu geladen werden muss.

Mögliche vorherige Status:
ausgeblendet (über das Ereignis freeze)

Mögliche nächste Status:
aktiv (über das Ereignis resume, dann das Ereignis pageshow ausgelöst)
passiv pageshow ausgelöst)
{1
nicht ausgelöst Ereignis ausgelöst
resume

Beendet

Eine Seite ist im Status Beendet, sobald sie vom Browser entladen und aus dem Speicher gelöscht wurde. In diesem Status können keine neuen Aufgaben gestartet werden. Laufende Aufgaben können abgebrochen werden, wenn sie zu lange ausgeführt werden.

Mögliche vorherige Status:
ausgeblendet (über das Ereignis pagehide)

Mögliche nächste Status:
KEINE

Verworfen

Eine Seite hat den Status Verworfen, wenn sie vom Browser entladen wird, um Ressourcen zu sparen. In diesem Status können keine Aufgaben, Ereignis-Callbacks oder JavaScript ausgeführt werden, da Verwerfen in der Regel unter Ressourceneinschränkungen erfolgen, bei denen das Starten neuer Prozesse nicht möglich ist.

Im Status Verworfen ist der Tab selbst (einschließlich Tabtitel und Favicon) für den Nutzer normalerweise sichtbar, obwohl die Seite verschwunden ist.

Mögliche vorherige Status:
hidden (keine Ereignisse ausgelöst)
eingefroren (keine Ereignisse ausgelöst)

Mögliche nächste Status:
KEINE

Veranstaltungen

Browser senden viele Ereignisse, aber nur ein kleiner Teil signalisiert eine mögliche Änderung des Seitenlebenszyklusstatus. In der folgenden Tabelle finden Sie einen Überblick über alle Ereignisse, die sich auf den Lebenszyklus beziehen, sowie die Status, zwischen denen sie wechseln können.

Name Details
focus

Ein DOM-Element wurde im Fokus.

Hinweis:Ein focus-Ereignis signalisiert nicht unbedingt eine Statusänderung. Eine Statusänderung wird nur dann signalisiert, wenn die Seite zuvor keinen Eingabefokus hatte.

Mögliche vorherige Status:
passiv

Mögliche aktuelle Status:
aktiv

blur

Ein DOM-Element ist nicht mehr im Fokus.

Hinweis:Ein blur-Ereignis signalisiert nicht unbedingt eine Statusänderung. Sie signalisiert nur dann eine Statusänderung, wenn der Fokus nicht mehr auf der Seite liegt (d.h., die Seite hat nicht einfach den Fokus von einem Element auf ein anderes verschoben).

Mögliche vorherige Status:
aktiv

Mögliche aktuelle Status:
passiv

visibilitychange

Der Wert visibilityState des Dokuments hat sich geändert. Das kann passieren, wenn ein Nutzer eine neue Seite aufruft, den Tab wechselt, einen Tab schließt, den Browser minimiert oder schließt oder wenn er auf mobilen Betriebssystemen zwischen Apps wechselt.

Mögliche vorherige Status:
passiv
verborgen

Mögliche aktuelle Status:
passiv
verborgen

freeze *

Die Seite ist gerade eingefroren. Fixierbare Aufgaben in den Aufgabenwarteschlangen der Seite werden nicht gestartet.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
eingefroren

resume *

Der Browser hat eine eingefrorene Seite fortgesetzt.

Mögliche vorherige Status:
eingefroren

Mögliche aktuelle Status:
aktiv (wenn das Ereignis pageshow folgt)
passiv (wenn das Ereignis pageshow folgt)
hidden

pageshow

Ein Sitzungsverlaufseintrag wird durchlaufen.

Dabei kann es sich um einen komplett neuen Seitenaufbau oder eine aus dem Back-Forward-Cache entnommene Seite handeln. Wenn die Seite aus dem Back-Forward-Cache entnommen wurde, lautet das persisted-Attribut des Ereignisses true. Andernfalls ist es false.

Mögliche vorherige Status:
eingefroren (ein resume-Ereignis hätte ebenfalls ausgelöst)

Mögliche aktuelle Status:
aktiv
passiv
ausgeblendet

pagehide

Ein Sitzungsverlaufseintrag wird durchlaufen.

Wenn der Nutzer zu einer anderen Seite geht und der Browser die aktuelle Seite dem Back-Forward-Cache hinzufügen kann, um sie später wiederzuverwenden, lautet das persisted-Attribut des Ereignisses true. Wenn true, wird die Seite in den eingefrorenen-Status verschoben, andernfalls in den beendeten-Status.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
eingefroren (event.persisted ist wahr, freeze-Ereignis folgt)
beendet (event.persisted ist falsch, unload Ereignis folgt)

beforeunload

Das Fenster, das Dokument und seine Ressourcen werden entladen. Das Dokument ist weiterhin sichtbar und die Veranstaltung kann zu diesem Zeitpunkt immer noch abgesagt werden.

Wichtig:Das Ereignis beforeunload sollte nur verwendet werden, um Nutzer über nicht gespeicherte Änderungen zu informieren. Sobald diese Änderungen gespeichert sind, sollte das Ereignis entfernt werden. Es sollte niemals ohne Bedingungen zur Seite hinzugefügt werden, da sich dies in manchen Fällen negativ auf die Leistung auswirken kann. Weitere Informationen finden Sie im Abschnitt zu Legacy-APIs.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
Beendet

unload

Die Seite wird entladen.

Warnung:Die Verwendung des Ereignisses unload wird nicht empfohlen, da es unzuverlässig ist und in einigen Fällen die Leistung beeinträchtigen kann. Weitere Informationen finden Sie im Abschnitt zu Legacy-APIs.

Mögliche vorherige Status:
ausgeblendet

Mögliche aktuelle Status:
Beendet

* Gibt ein neues Ereignis an, das von der Page Lifecycle API definiert wurde

Neue Funktionen in Chrome 68

Das vorherige Diagramm zeigt zwei Status, die vom System initiiert und nicht vom Nutzer initiiert werden: eingefroren und verworfen. Wie bereits erwähnt, frieren Browser heute gelegentlich ein und verwerfen verborgene Tabs nach eigenem Ermessen. Entwickler wissen jedoch nicht, wann das passiert.

In Chrome 68 können Entwickler jetzt beobachten, ob ein ausgeblendeter Tab eingefroren ist und wieder aktiviert wird, indem sie auf die Ereignisse freeze und resume auf document warten.

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

Ab Chrome 68 enthält das document-Objekt in der Desktopversion von Chrome jetzt die Property wasDiscarded (Unterstützung von Android wird in diesem Problem erfasst). Wenn Sie feststellen möchten, ob eine Seite auf einem ausgeblendeten Tab verworfen wurde, können Sie den Wert dieses Attributs zum Zeitpunkt des Seitenaufbaus prüfen. Hinweis: Verworfene Seiten müssen neu geladen werden, bevor sie noch einmal verwendet werden können.

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

Informationen dazu, welche Maßnahmen bei den freeze- und resume-Ereignissen wichtig sind und wie du dich darauf vorbereiten kannst, dass Seiten verworfen werden, findest du in den Entwicklerempfehlungen für die einzelnen Status.

In den nächsten Abschnitten erhalten Sie einen Überblick darüber, wie diese neuen Funktionen in die vorhandenen Status und Ereignisse der Webplattform passen.

Seitenlebenszyklusstatus im Code beobachten

In den Status Aktiv, Passiv und Ausgeblendet kann JavaScript-Code ausgeführt werden, der den aktuellen Status des Seitenlebenszyklus aus vorhandenen Webplattform-APIs bestimmt.

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

Die Status eingefroren und beendet können dagegen nur in ihrem jeweiligen Event-Listener (freeze und pagehide) erkannt werden, wenn sich der Status ändert.

Statusänderungen beobachten

Aufbauend auf der zuvor definierten Funktion getState() können Sie mit dem folgenden Code alle Statusänderungen des Seitenlebenszyklus beobachten.

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// Options used for all event listeners.
const opts = {capture: true};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState(), opts));
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, opts);

window.addEventListener('pagehide', (event) => {
  // If the event's persisted property is `true` the page is about
  // to enter the back/forward cache, which is also in the frozen state.
  // If the event's persisted property is not `true` the page is
  // about to be unloaded.
  logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);

Dieser Code erfüllt drei Funktionen:

  • Legt den Anfangszustand mit der Funktion getState() fest.
  • Definiert eine Funktion, die einen nächsten Status akzeptiert. Bei einer Änderung werden die Statusänderungen in der Console protokolliert.
  • Es werden erfassende Ereignis-Listener für alle erforderlichen Lebenszyklusereignisse hinzugefügt, die wiederum logStateChange() aufrufen und den nächsten Status übergeben.

Bei dem Code ist jedoch zu beachten, dass alle Event-Listener zu window hinzugefügt werden und alle {capture: true} übergeben. Das kann verschiedene Gründe haben:

  • Nicht alle Ereignisse im Seitenlebenszyklus haben dasselbe Ziel. pagehide und pageshow werden bei window ausgelöst. visibilitychange, freeze und resume werden unter document ausgelöst, während focus und blur auf ihren jeweiligen DOM-Elementen ausgelöst werden.
  • Die meisten dieser Ereignisse werden nicht als Bubble angezeigt. Daher ist es nicht möglich, einem gemeinsamen Ancestor-Element nicht erfassende Event-Listener hinzuzufügen und alle zu beobachten.
  • Die Erfassungsphase wird vor der Ziel- oder Bubble-Phase ausgeführt. Wenn Sie dort Listener hinzufügen, wird sichergestellt, dass sie ausgeführt werden, bevor anderer Code sie abbrechen kann.

Entwicklerempfehlungen für die einzelnen Bundesstaaten

Als Entwickler ist es wichtig, die Status des Seitenlebenszyklus zu verstehen und zu wissen, wie sie im Code beobachtet werden, da die Art der Arbeit, die Sie ausführen sollten (und nicht), weitgehend davon abhängt, in welchem Zustand sich Ihre Seite befindet.

Es ist beispielsweise eindeutig nicht sinnvoll, dem Nutzer eine vorübergehende Benachrichtigung anzuzeigen, wenn die Seite ausgeblendet ist. Dieses Beispiel ist zwar ziemlich offensichtlich, es gibt aber noch weitere Empfehlungen, die nicht so offensichtlich sind und es wert sind, sie aufzuführen.

Status Empfehlungen für Entwickler
Active

Der Status Aktiv ist die kritischste Zeit für den Nutzer und daher die wichtigste Zeit, in der deine Seite auf Nutzereingaben reagieren muss.

Alle Vorgänge, die nicht auf der Benutzeroberfläche basieren und die den Hauptthread blockieren, sollten herabgestuft werden. Sie sollten Zeiträume bei Inaktivität einstufen oder auf einen Web-Worker auslagern.

Passive

Im passiven Status interagiert der Nutzer nicht mit der Seite, kann sie aber trotzdem sehen. UI-Aktualisierungen und Animationen sollten also weiterhin reibungslos sein, aber der Zeitpunkt dieser Aktualisierungen ist weniger wichtig.

Wenn die Seite von aktiv zu passiv wechselt, empfiehlt es sich, den nicht gespeicherten Anwendungsstatus beizubehalten.

Hidden

Wenn sich die Seite von passiv zu verborgen ändert, interagiert der Nutzer möglicherweise erst wieder mit ihr, nachdem sie neu geladen wurde.

Der Wechsel zu verborgen ist häufig die letzte Statusänderung, die von Entwicklern zuverlässig beobachtet werden kann. Das gilt insbesondere auf Mobilgeräten, da Nutzer Tabs oder die Browser-App schließen können und die Ereignisse beforeunload, pagehide und unload in diesen Fällen nicht ausgelöst werden.

Das bedeutet, dass Sie den Status hidden als wahrscheinliches Ende der Nutzersitzung behandeln sollten. Das heißt, Sie müssen den nicht gespeicherten Anwendungsstatus beibehalten und alle nicht gesendeten Analysedaten senden.

Sie sollten auch keine Aktualisierungen der UI mehr vornehmen (da diese für den Nutzer nicht sichtbar sind) und alle Aufgaben beenden, die nicht im Hintergrund ausgeführt werden sollen.

Frozen

Im Status Eingefroren werden fixierbare Aufgaben in den Aufgabenwarteschlangen angehalten, bis die Seite nicht mehr fixiert ist. Dies kann z. B. nicht geschehen, wenn die Seite verworfen wird.

Wenn also die Seite von ausgeblendet zu eingefroren wechselt, müssen Sie alle Timer stoppen oder alle Verbindungen trennen, die sich auf andere geöffnete Tabs im selben Ursprung auswirken oder die Fähigkeit des Browsers beeinträchtigen könnten, die Seite in den Back-Forward-Cache zu speichern.

Insbesondere ist es wichtig, dass Sie:

  • Schließen Sie alle offenen IndexedDB-Verbindungen.
  • Offene BroadcastChannel-Verbindungen beenden.
  • Aktive WebRTC-Verbindungen trennen.
  • Beenden Sie alle Netzwerkabfragen oder schließen Sie alle offenen WebSocket-Verbindungen.
  • Lassen Sie alle zurückgehaltenen Web Locks los.

Sie sollten auch jeden dynamischen Ansichtsstatus (z.B. Scrollposition in einer unendlichen Listenansicht) zu sessionStorage (oder IndexedDB über commit()) beibehalten, der wiederhergestellt werden soll, wenn die Seite verworfen und später neu geladen werden soll.

Wenn die Seite von eingefroren zu verborgen wechselt, können Sie geschlossene Verbindungen wieder öffnen oder alle Abfragen neu starten, die Sie angehalten haben, als die Seite ursprünglich eingefroren war.

Terminated

Normalerweise müssen Sie nichts weiter unternehmen, wenn eine Seite in den Status Beendet wechselt.

Da Seiten, die als Folge einer Nutzeraktion entladen werden, immer den Status Ausgeblendet haben, bevor in den Status Beendet gewechselt wird, wird im Status Ausgeblendet die Logik für das Beenden der Sitzung ausgeführt, z.B. die Speicherung des Anwendungsstatus und die Meldung an die Analyse.

Wie in den Empfehlungen für den ausgeblendeten Status erwähnt, ist es für Entwickler sehr wichtig zu erkennen, dass der Übergang zum Status beendet in vielen Fällen nicht zuverlässig erkannt werden kann (insbesondere auf Mobilgeräten). Daher verlieren Entwickler, die von Beendigungsereignissen abhängig sind (z. B. beforeunload, pagehide und unload), wahrscheinlich Daten.

Discarded

Der Status discarded (Verworfen) kann von Entwicklern zum Zeitpunkt des Verwerfens einer Seite nicht beobachtet werden. Dies liegt daran, dass Seiten in der Regel aufgrund von Ressourceneinschränkungen verworfen werden. Das Aufheben der Fixierung einer Seite nur für die Ausführung des Skripts als Reaktion auf ein Verwerfungsereignis ist in den meisten Fällen einfach nicht möglich.

Daher solltest du dich auf die Möglichkeit einer Verwerfung der Änderung von versteckt zu eingefroren vorbereiten und dann auf die Wiederherstellung einer verworfenen Seite zum Zeitpunkt der Seitenladezeit reagieren, indem du document.wasDiscarded aktivierst.

Auch hier ist die Zuverlässigkeit und Reihenfolge von Lebenszyklus-Ereignissen nicht in allen Browsern einheitlich implementiert. Daher ist es am einfachsten, den Hinweisen in der Tabelle mit PageLifecycle.js zu folgen.

Zu vermeidende Legacy-Lebenszyklus-APIs

Die folgenden Ereignisse sollten möglichst vermieden werden.

Das Unload-Ereignis

Viele Entwickler behandeln das unload-Ereignis als garantierten Callback und verwenden es als Signal am Ende der Sitzung, um den Status zu speichern und Analysedaten zu senden. Dies ist jedoch äußerst unzuverlässig, insbesondere auf Mobilgeräten. Das Ereignis unload wird in vielen typischen Entlade-Situationen nicht ausgelöst, z. B. beim Schließen eines Tabs über den Tab-Wechsler auf einem Mobilgerät oder beim Schließen der Browser-App über den App-Wechsler.

Aus diesem Grund ist es immer besser, sich auf das visibilitychange-Ereignis zu verlassen, um festzustellen, wann eine Sitzung endet. Der ausgeblendete Zustand ist die letzte zuverlässige Zeit zum Speichern von App- und Nutzerdaten.

Das reine Vorhandensein eines registrierten unload-Event-Handlers (über onunload oder addEventListener()) kann außerdem verhindern, dass Browser Seiten im Back-Forward-Cache speichern, um das Laden zu beschleunigen.

In allen modernen Browsern empfiehlt es sich, statt des unload-Ereignisses immer das Ereignis pagehide zu verwenden, um mögliche Seitenentladungen (im Status Beendet) zu erkennen. Wenn Internet Explorer 10 oder niedriger unterstützt werden muss, sollten Sie das pagehide-Ereignis erkennen und unload nur verwenden, wenn der Browser pagehide nicht unterstützt:

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

window.addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
});

Das „beforeunload“-Ereignis

Beim beforeunload-Ereignis gibt es ein ähnliches Problem wie beim unload-Ereignis. Insofern kann das Vorhandensein eines beforeunload-Ereignisses in der Vergangenheit verhindern, dass Seiten den Back-Forward-Cache verwenden können. Bei modernen Browsern ist diese Einschränkung nicht möglich. Einige Browser lösen jedoch als Vorsichtsmaßnahme das beforeunload-Ereignis nicht aus, wenn versucht wird, eine Seite in den Back-Forward-Cache zu speichern. Das bedeutet, dass das Ereignis als Signal am Ende der Sitzung nicht zuverlässig ist. Außerdem erfordern einige Browser (einschließlich Chrome) eine Nutzerinteraktion auf der Seite, bevor das beforeunload-Ereignis ausgelöst wird, was die Zuverlässigkeit weiter beeinträchtigt.

Ein Unterschied zwischen beforeunload und unload besteht darin, dass beforeunload legitim verwendet wird. Wenn Sie beispielsweise den Nutzer warnen möchten, dass nicht gespeicherte Änderungen verloren gehen, wenn er die Seite weiterhin entlädt.

Da es gute Gründe für die Verwendung von beforeunload gibt, sollten Sie beforeunload-Listener nur dann hinzufügen, wenn ein Nutzer nicht gespeicherte Änderungen hat, und sie sofort nach dem Speichern entfernen.

Das sollten Sie also nicht tun, da er bedingungslos einen beforeunload-Listener hinzufügt:

addEventListener('beforeunload', (event) => {
  // A function that returns `true` if the page has unsaved changes.
  if (pageHasUnsavedChanges()) {
    event.preventDefault();

    // Legacy support for older browsers.
    return (event.returnValue = true);
  }
});

Gehen Sie stattdessen so vor (den beforeunload-Listener wird nur bei Bedarf hinzugefügt und entfernt, wenn er nicht erforderlich ist):

const beforeUnloadListener = (event) => {
  event.preventDefault();
  
  // Legacy support for older browsers.
  return (event.returnValue = true);
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  removeEventListener('beforeunload', beforeUnloadListener);
});

Häufig gestellte Fragen

Warum gibt es keinen Status „Wird geladen“?

Die Page Lifecycle API definiert Status als diskret und schließen sich gegenseitig aus. Da eine Seite im aktiven, passiven oder ausgeblendeten Zustand geladen werden kann und sich ihr Status ändern oder sogar beendet werden kann, bevor der Ladevorgang abgeschlossen ist, ist ein separater Ladestatus bei diesem Modell nicht sinnvoll.

Meine Seite funktioniert, wenn sie ausgeblendet ist. Wie kann ich verhindern, dass sie einfriert oder verworfen wird?

Es gibt viele legitime Gründe, warum Webseiten nicht eingefroren sein sollten, wenn sie im verborgenen Zustand ausgeführt werden. Das naheliegendste Beispiel ist eine App, die Musik abspielt.

Es gibt auch Situationen, in denen es riskant wäre, eine Seite von Chrome zu verwerfen, z. B. wenn sie ein Formular mit nicht gesendeten Nutzereingaben enthält oder wenn ein beforeunload-Handler vorhanden ist, der beim Entfernen der Seite eine Warnung sendet.

Im Moment wird Chrome konservativ vorgehen, wenn Seiten verworfen werden. Dies geschieht nur, wenn davon auszugehen ist, dass dies keine Auswirkungen auf die Nutzer hat. Beispielsweise werden Seiten, die im ausgeblendeten Status eine der folgenden Aktionen ausgeführt haben, nur dann verworfen, wenn sie unter extremen Ressourceneinschränkungen auftreten:

  • Audio wird wiedergegeben
  • WebRTC verwenden
  • Tabellentitel oder Favicon aktualisieren
  • Benachrichtigungen werden angezeigt
  • Push-Benachrichtigungen senden

Die aktuellen Listenfunktionen, mit denen bestimmt wird, ob ein Tab sicher eingefroren oder verworfen werden kann, finden Sie unter Heuristik zum Einfrieren und Verwerfen in Chrome.

Was ist der Back-Forward-Cache?

Der Back-Forward-Cache ist ein Begriff, der eine Navigationsoptimierung beschreibt, die in einigen Browsern implementiert wird, um die Verwendung der Zurück- und Vorwärts-Schaltflächen zu beschleunigen.

Wenn ein Nutzer eine Seite verlässt, wird in diesen Browsern eine Version dieser Seite eingefroren, sodass sie schnell fortgesetzt werden kann, falls der Nutzer über die Schaltflächen „Zurück“ oder „Vorwärts“ zurückkehrt. Denken Sie daran, dass durch das Hinzufügen eines unload-Event-Handlers diese Optimierung nicht möglich ist.

Dieses Einfrieren ist in jeder Hinsicht die gleiche wie die einfrierenden Browser, um CPU und Akku zu schonen. Aus diesem Grund wird es als Teil des eingefrorenen Lebenszyklusstatus betrachtet.

Wie kann ich Daten in IndexedDB speichern, wenn ich keine asynchronen APIs im eingefrorenen oder beendeten Zustand ausführen kann?

Im eingefrorenen oder beendeten Zustand werden einfrierende Aufgaben in den Aufgabenwarteschlangen einer Seite ausgesetzt. Das bedeutet, dass asynchrone und Callback-basierte APIs wie IndexedDB nicht zuverlässig verwendet werden können.

In Zukunft werden wir IDBTransaction-Objekten eine commit()-Methode hinzufügen, die Entwicklern die Möglichkeit gibt, praktisch nur schreibgeschützte Transaktionen auszuführen, für die keine Callbacks erforderlich sind. Wenn der Entwickler also nur Daten in IndexedDB schreibt und keine komplexe Transaktion aus Lese- und Schreibvorgängen durchführt, kann die Methode commit() beendet werden, bevor Aufgabenwarteschlangen angehalten werden (vorausgesetzt, die IndexedDB-Datenbank ist bereits geöffnet).

Für Code, der heute funktionieren muss, haben Entwickler jedoch zwei Möglichkeiten:

  • Sitzungsspeicher verwenden:Sitzungsspeicher ist synchron und wird beim Verwerfen von Seiten beibehalten.
  • IndexedDB im Service Worker verwenden:Ein Service Worker kann Daten in IndexedDB speichern, nachdem die Seite beendet oder verworfen wurde. Im Event-Listener freeze oder pagehide können Sie über postMessage() Daten an Ihren Service Worker senden. Der Service Worker kann die Daten dann speichern.

App im eingefrorenen und verworfenen Zustand testen

Wenn Sie testen möchten, wie sich Ihre App im eingefrorenen und verworfenen Zustand verhält, können Sie chrome://discards aufrufen, um geöffnete Tabs zu fixieren oder zu verwerfen.

Chrome „Verwirft die UI“
Chrome „Verwirft die UI“ in Chrome

So lässt sich dafür sorgen, dass Ihre Seite die Ereignisse freeze und resume sowie das Flag document.wasDiscarded korrekt verarbeitet, wenn Seiten nach dem Verwerfen neu geladen werden.

Zusammenfassung

Entwickler, die die Systemressourcen der Geräte ihrer Nutzer berücksichtigen möchten, sollten ihre Apps unter Berücksichtigung des Seitenlebenszyklusstatus erstellen. Es ist wichtig, dass Webseiten nicht übermäßig viele Systemressourcen in Situationen verbrauchen, die der Nutzer nicht erwartet.

Je mehr Entwickler mit der Implementierung der neuen Page Lifecycle APIs beginnen, desto sicherer wird es für Browser sein, eingefroren und ungenutzte Seiten zu verwerfen. Dies bedeutet, dass Browser weniger Arbeitsspeicher, CPU, Akku und Netzwerkressourcen verbrauchen, was für die Nutzer ein Gewinn ist.