Standardisierung des clientseitigen Routings durch eine brandneue API, die die Entwicklung von Single-Page-Anwendungen grundlegend verändert.
Single-Page-Anwendungen (SPAs) zeichnen sich durch eine Kernfunktion aus: Sie schreiben ihre Inhalte dynamisch um, während der Nutzer mit der Website interagiert. Das ist anders als bei der Standardmethode, bei der vollständig neue Seiten vom Server geladen werden.
Bei SPAs war diese Funktion zwar über die History API (oder in begrenzten Fällen durch Anpassen des #Hash-Teils der Website) möglich, aber es handelt sich um eine umständliche API, die lange vor der Normierung von SPAs entwickelt wurde. Das Web schreit nach einem völlig neuen Ansatz. Die Navigation API ist eine vorgeschlagene API, die diesen Bereich komplett überarbeitet, anstatt nur die Schwachstellen der History API zu beheben. Beispiel: Scroll Restoration hat die History API gepatcht, anstatt sie neu zu erfinden.
In diesem Beitrag wird die Navigation API allgemein beschrieben. Den technischen Vorschlag finden Sie im Entwurfsbericht im WICG-Repository.
Nutzungsbeispiel
Wenn Sie die Navigation API verwenden möchten, fügen Sie zuerst einen "navigate"
-Listener für das globale navigation
-Objekt hinzu.
Dieses Ereignis ist grundsätzlich zentralisiert: Es wird für alle Arten von Navigationsvorgängen ausgelöst, unabhängig davon, ob der Nutzer eine Aktion ausgeführt hat (z. B. auf einen Link geklickt, ein Formular gesendet oder vor- und zurückgegangen ist) oder ob die Navigation programmatisch (d. h. über den Code Ihrer Website) ausgelöst wird.
In den meisten Fällen können Sie mit Ihrem Code das Standardverhalten des Browsers für diese Aktion überschreiben.
Bei SPAs bedeutet das in der Regel, dass der Nutzer auf derselben Seite bleibt und die Inhalte der Website geladen oder geändert werden.
Ein NavigateEvent
wird an den "navigate"
-Listener übergeben, der Informationen zur Navigation enthält, z. B. die Ziel-URL. So können Sie an einem zentralen Ort auf die Navigation reagieren.
Ein einfacher "navigate"
-Listener könnte so aussehen:
navigation.addEventListener('navigate', navigateEvent => {
// Exit early if this navigation shouldn't be intercepted.
// The properties to look at are discussed later in the article.
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname === '/') {
navigateEvent.intercept({handler: loadIndexPage});
} else if (url.pathname === '/cats/') {
navigateEvent.intercept({handler: loadCatsPage});
}
});
Sie haben zwei Möglichkeiten, die Navigation zu gestalten:
- Rufen Sie
intercept({ handler })
(wie oben beschrieben) auf, um die Navigation zu verarbeiten. - Rufen Sie
preventDefault()
auf, um die Navigation vollständig abzubrechen.
In diesem Beispiel wird intercept()
für das Ereignis aufgerufen.
Der Browser ruft Ihren handler
-Callback auf, der den nächsten Status Ihrer Website konfigurieren sollte.
Dadurch wird ein Übergangsobjekt (navigation.transition
) erstellt, mit dem der Fortschritt der Navigation verfolgt werden kann.
Sowohl intercept()
als auch preventDefault()
sind in der Regel zulässig, es gibt jedoch Fälle, in denen sie nicht aufgerufen werden können.
Sie können Navigationsaktionen nicht über intercept()
verarbeiten, wenn es sich um eine ursprungsübergreifende Navigation handelt.
Sie können eine Navigation über preventDefault()
nicht abbrechen, wenn der Nutzer in seinem Browser auf die Schaltfläche „Zurück“ oder „Vorwärts“ klickt. Nutzer dürfen nicht auf Ihrer Website „gefangen“ werden.
(Diese Änderung wird auf GitHub diskutiert.)
Auch wenn Sie die Navigation selbst nicht beenden oder abfangen können, wird das "navigate"
-Ereignis trotzdem ausgelöst.
Es ist informativ. Ihr Code könnte beispielsweise ein Analytics-Ereignis protokollieren, um anzugeben, dass ein Nutzer Ihre Website verlässt.
Warum sollte ich ein weiteres Event auf der Plattform hinzufügen?
Ein "navigate"
-Event-Listener zentralisiert die Verarbeitung von URL-Änderungen in einer SPA.
Mit älteren APIs ist das schwierig.
Wenn Sie schon einmal das Routing für Ihre eigene SPA mit der History API geschrieben haben, haben Sie möglicherweise Code wie diesen hinzugefügt:
function updatePage(event) {
event.preventDefault(); // we're handling this link
window.history.pushState(null, '', event.target.href);
// TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));
Das ist in Ordnung, aber nicht vollständig. Links auf Ihrer Seite können sich ändern und sind nicht die einzige Möglichkeit für Nutzer, durch Seiten zu navigieren. Sie können beispielsweise ein Formular senden oder eine Bild-Map verwenden. Ihre Seite befasst sich möglicherweise mit diesen, aber es gibt eine Vielzahl von Möglichkeiten, die vereinfacht werden könnten – etwas, das mit der neuen Navigation API erreicht wird.
Außerdem wird die Back/Forward-Navigation nicht berücksichtigt. Dafür gibt es ein anderes Ereignis: "popstate"
.
Ich persönlich habe den Eindruck, dass die History API einiges dazu beitragen könnte, diese Möglichkeiten zu nutzen.
Es hat jedoch nur zwei Oberflächen: Reagieren, wenn der Nutzer in seinem Browser auf „Zurück“ oder „Vorwärts“ klickt, sowie das Pushen und Ersetzen von URLs.
Es gibt keine Analogie zu "navigate"
, es sei denn, Sie richten beispielsweise manuell Listener für Klickereignisse ein, wie oben gezeigt.
Umgang mit der Navigation
Die navigateEvent
enthält viele Informationen zur Navigation, anhand derer Sie entscheiden können, wie Sie mit einer bestimmten Navigation umgehen.
Die wichtigsten Eigenschaften sind:
canIntercept
- Wenn dieser Wert „false“ ist, können Sie den Navigationsvorgang nicht abfangen. Zonenübergreifende Navigationsvorgänge und Traversierungen zwischen Dokumenten können nicht abgefangen werden.
destination.url
- Wahrscheinlich die wichtigste Information, die bei der Navigation berücksichtigt werden muss.
hashChange
- Wahr, wenn die Navigation im selben Dokument erfolgt und sich die URL nur durch den Hash von der aktuellen URL unterscheidet.
In modernen SPAs sollte der Hash zum Verknüpfen mit verschiedenen Teilen des aktuellen Dokuments verwendet werden. Wenn
hashChange
also „true“ ist, müssen Sie diese Navigation wahrscheinlich nicht abfangen. downloadRequest
- Wenn „true“, wurde die Navigation durch einen Link mit dem Attribut
download
initiiert. In den meisten Fällen müssen Sie dies nicht abfangen. formData
- Wenn dieser Wert nicht null ist, gehört diese Navigation zu einer POST-Formularübermittlung.
Berücksichtigen Sie dies bei der Navigation.
Wenn Sie nur GET-Navigationen verarbeiten möchten, sollten Sie keine Navigationen abfangen, bei denen
formData
nicht null ist. Ein Beispiel für die Verarbeitung von Formulareinreichungen finden Sie weiter unten im Artikel. navigationType
- Das ist eine der folgenden Optionen:
"reload"
,"push"
,"replace"
oder"traverse"
. Wenn es"traverse"
ist, kann diese Navigation nicht überpreventDefault()
abgebrochen werden.
Die im ersten Beispiel verwendete shouldNotIntercept
-Funktion könnte beispielsweise so aussehen:
function shouldNotIntercept(navigationEvent) {
return (
!navigationEvent.canIntercept ||
// If this is just a hashChange,
// just let the browser handle scrolling to the content.
navigationEvent.hashChange ||
// If this is a download,
// let the browser perform the download.
navigationEvent.downloadRequest ||
// If this is a form submission,
// let that go to the server.
navigationEvent.formData
);
}
Abfangen
Wenn in Ihrem Code intercept({ handler })
aus dem "navigate"
-Listener aufgerufen wird, wird dem Browser mitgeteilt, dass die Seite jetzt für den neuen, aktualisierten Status vorbereitet wird und die Navigation einige Zeit dauern kann.
Der Browser erfasst zuerst die Scrollposition für den aktuellen Status, damit sie später optional wiederhergestellt werden kann. Anschließend wird Ihr handler
-Callback aufgerufen.
Wenn Ihre handler
ein Promise zurückgibt (was bei async-Funktionen automatisch geschieht), teilt dieses Promise dem Browser mit, wie lange die Navigation dauert und ob sie erfolgreich ist.
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
Daher wird mit dieser API ein semantisches Konzept eingeführt, das der Browser versteht: Eine SPA-Navigation findet gerade statt und ändert das Dokument im Laufe der Zeit von einer vorherigen URL und einem vorherigen Status zu einer neuen URL und einem neuen Status. Das bietet eine Reihe potenzieller Vorteile, darunter die Barrierefreiheit: Browser können den Beginn, das Ende oder einen möglichen Fehler bei einer Navigation anzeigen. In Chrome wird beispielsweise der native Ladeindikator aktiviert und der Nutzer kann auf die Schaltfläche „Stopp“ klicken. (Das passiert derzeit nicht, wenn der Nutzer über die Schaltflächen „Zurück“/„Vorwärts“ navigiert, wird aber bald behoben.)
Navigation bestätigen
Beim Abfangen von Navigationsvorgängen wird die neue URL kurz vor dem Aufrufen Ihres handler
-Callbacks wirksam.
Wenn Sie das DOM nicht sofort aktualisieren, wird für einen bestimmten Zeitraum der alte Inhalt zusammen mit der neuen URL angezeigt.
Dies wirkt sich unter anderem auf die Auflösung relativer URLs beim Abrufen von Daten oder beim Laden neuer untergeordneter Ressourcen aus.
Eine Möglichkeit, die URL-Änderung zu verzögern, wird auf GitHub diskutiert. Es wird jedoch im Allgemeinen empfohlen, die Seite sofort mit einem Platzhalter für die eingehenden Inhalte zu aktualisieren:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
So werden nicht nur Probleme mit der URL-Auflösung vermieden, sondern es fühlt sich auch schnell an, da Sie sofort auf den Nutzer reagieren.
Abbruchsignale
Da Sie asynchrone Aufgaben in einem intercept()
-Handler ausführen können, kann die Navigation überflüssig werden.
Das passiert in folgenden Fällen:
- Der Nutzer klickt auf einen anderen Link oder ein Code führt eine andere Navigation aus. In diesem Fall wird die alte Navigation zugunsten der neuen Navigation aufgegeben.
- Der Nutzer klickt im Browser auf die Schaltfläche „Stopp“.
Um auf diese Möglichkeiten zu reagieren, enthält das an den "navigate"
-Listener übergebene Ereignis die Eigenschaft signal
, die ein AbortSignal
ist.
Weitere Informationen finden Sie unter Abbruchfähiger Abruf.
Kurz gesagt: Es wird ein Objekt bereitgestellt, das ein Ereignis auslöst, wenn Sie Ihre Arbeit beenden sollten.
Sie können AbortSignal
an alle Aufrufe von fetch()
übergeben. Dadurch werden laufende Netzwerkanfragen abgebrochen, wenn die Navigation unterbrochen wird.
Dadurch wird sowohl die Bandbreite des Nutzers geschont als auch die von fetch()
zurückgegebene Promise
abgelehnt. So wird verhindert, dass nachfolgender Code Aktionen wie das Aktualisieren des DOM ausführt, um eine jetzt ungültige Seitennavigation anzuzeigen.
Hier ist das vorherige Beispiel, aber mit inline eingefügtem getArticleContent
. Es wird gezeigt, wie AbortSignal
mit fetch()
verwendet werden kann:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContentURL = new URL(
'/get-article-content',
location.href
);
articleContentURL.searchParams.set('path', url.pathname);
const response = await fetch(articleContentURL, {
signal: navigateEvent.signal,
});
const articleContent = await response.json();
renderArticlePage(articleContent);
},
});
}
});
Scrollen
Wenn Sie intercept()
eine Navigation ausführen, versucht der Browser, das Scrollen automatisch zu verarbeiten.
Bei Navigationsvorgängen zu einem neuen Verlaufseintrag (wenn navigationEvent.navigationType
gleich "push"
oder "replace"
ist) wird versucht, zu dem Teil zu scrollen, der durch das URL-Fragment (der Teil nach dem #
) angegeben wird, oder der Scrollvorgang wird auf den Anfang der Seite zurückgesetzt.
Bei Neuladen und Durchlaufen bedeutet das, dass die Scrollposition auf den Punkt zurückgesetzt wird, an dem dieser Verlaufseintrag zuletzt angezeigt wurde.
Standardmäßig geschieht dies, sobald das von handler
zurückgegebene Promise aufgelöst wird. Wenn es sinnvoll ist, früher zu scrollen, können Sie navigateEvent.scroll()
aufrufen:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
navigateEvent.scroll();
const secondaryContent = await getSecondaryContent(url.pathname);
addSecondaryContent(secondaryContent);
},
});
}
});
Alternativ können Sie die automatische Scrollbehandlung vollständig deaktivieren, indem Sie die Option scroll
von intercept()
auf "manual"
setzen:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
Fokusverwaltung
Sobald das von Ihrem handler
zurückgegebene Promise aufgelöst wird, fokussiert der Browser das erste Element mit dem Attribut autofocus
oder das <body>
-Element, wenn kein Element dieses Attribut hat.
Sie können dieses Verhalten deaktivieren, indem Sie die Option focusReset
von intercept()
auf "manual"
setzen:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
Erfolgs- und Fehlerereignisse
Wenn Ihr intercept()
-Handler aufgerufen wird, kann Folgendes passieren:
- Wenn die zurückgegebene
Promise
die Bedingung erfüllt (oder Sieintercept()
nicht aufgerufen haben), löst die Navigation API"navigatesuccess"
mit einerEvent
aus. - Wenn die zurückgegebenen
Promise
abgelehnt werden, wird über die API"navigateerror"
mit einemErrorEvent
ausgelöst.
Mit diesen Ereignissen kann Ihr Code Erfolg oder Misserfolg zentral verarbeiten. Bei Erfolg können Sie beispielsweise eine zuvor angezeigte Fortschrittsanzeige ausblenden:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
Oder Sie zeigen bei einem Fehler eine Fehlermeldung an:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
Der "navigateerror"
-Ereignis-Listener, der ein ErrorEvent
empfängt, ist besonders praktisch, da er garantiert alle Fehler aus Ihrem Code empfängt, mit dem eine neue Seite eingerichtet wird.
Sie können einfach await fetch()
, da der Fehler bei Nichtverfügbarkeit des Netzwerks schließlich an "navigateerror"
weitergeleitet wird.
Navigationseinträge
navigation.currentEntry
bietet Zugriff auf den aktuellen Eintrag.
Dieses Objekt beschreibt, wo sich der Nutzer gerade befindet.
Dieser Eintrag enthält die aktuelle URL, Metadaten, mit denen dieser Eintrag im Laufe der Zeit identifiziert werden kann, und den vom Entwickler angegebenen Status.
Die Metadaten enthalten key
, eine eindeutige String-Property jedes Eintrags, die den aktuellen Eintrag und seinen Slot darstellt.
Dieser Schlüssel bleibt auch dann gleich, wenn sich die URL oder der Status des aktuellen Eintrags ändert.
Es befindet sich weiterhin im selben Slot.
Wenn ein Nutzer hingegen auf „Zurück“ drückt und dann dieselbe Seite wieder öffnet, ändert sich key
, da durch diesen neuen Eintrag ein neuer Slot erstellt wird.
Für Entwickler ist key
nützlich, da die Navigation API es Ihnen ermöglicht, den Nutzer direkt zu einem Eintrag mit einem passenden Schlüssel zu navigieren.
Sie können es auch in den Status anderer Einträge beibehalten, um ganz einfach zwischen Seiten zu wechseln.
// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);
// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;
Status
Die Navigation API bietet ein Konzept des „Zustands“, das von Entwicklern bereitgestellte Informationen sind, die dauerhaft im aktuellen Verlaufseintrag gespeichert werden, aber für den Nutzer nicht direkt sichtbar sind.
Diese Funktion ist der Funktion history.state
in der History API sehr ähnlich, wurde aber verbessert.
In der Navigation API können Sie die Methode .getState()
des aktuellen Eintrags (oder eines beliebigen Eintrags) aufrufen, um eine Kopie seines Status zurückzugeben:
console.log(navigation.currentEntry.getState());
Standardmäßig ist dies undefined
.
Einstellungsstatus
Zustandsobjekte können zwar geändert werden, diese Änderungen werden jedoch nicht mit dem Verlaufseintrag gespeichert. Das bedeutet:
const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1
Der Status wird während der Skriptnavigation festgelegt:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
Dabei kann newState
ein beliebiges klonbares Objekt sein.
Wenn Sie den Status des aktuellen Eintrags aktualisieren möchten, ist es am besten, eine Navigation durchzuführen, die den aktuellen Eintrag ersetzt:
navigation.navigate(location.href, {state: newState, history: 'replace'});
Ihr "navigate"
-Ereignis-Listener kann diese Änderung dann über navigateEvent.destination
erfassen:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
Status synchron aktualisieren
Im Allgemeinen ist es besser, den Status asynchron über navigation.reload({state: newState})
zu aktualisieren. Der "navigate"
-Listener kann diesen Status dann anwenden. Manchmal ist die Statusänderung jedoch bereits vollständig angewendet, wenn Ihr Code davon erfährt, z. B. wenn der Nutzer ein <details>
-Element ein- oder ausblendet oder den Status einer Formulareingabe ändert. In diesen Fällen sollten Sie den Status aktualisieren, damit diese Änderungen bei Neuladen und Übergängen beibehalten werden. Das ist mit updateCurrentEntry()
möglich:
navigation.updateCurrentEntry({state: newState});
Außerdem gibt es eine Veranstaltung, auf der Sie mehr über diese Änderung erfahren können:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
Wenn Sie jedoch auf Statusänderungen in "currententrychange"
reagieren, teilen oder duplizieren Sie möglicherweise Ihren Code für die Statusverarbeitung zwischen dem "navigate"
-Ereignis und dem "currententrychange"
-Ereignis. Mit navigation.reload({state: newState})
können Sie ihn an einem Ort verarbeiten.
Status und URL-Parameter
Da der Status ein strukturiertes Objekt sein kann, ist es verlockend, ihn für den gesamten Anwendungsstatus zu verwenden. In vielen Fällen ist es jedoch besser, diesen Status in der URL zu speichern.
Wenn der Status beibehalten werden soll, wenn der Nutzer die URL mit einem anderen Nutzer teilt, speichern Sie ihn in der URL. Andernfalls ist das Statusobjekt die bessere Option.
Auf alle Einträge zugreifen
Das ist aber nicht alles.
Die API bietet auch die Möglichkeit, über den navigation.entries()
-Aufruf auf die gesamte Liste der Einträge zuzugreifen, die ein Nutzer beim Besuch Ihrer Website durchlaufen hat. Dabei wird ein Snapshot-Array von Einträgen zurückgegeben.
So lässt sich beispielsweise eine andere Benutzeroberfläche anzeigen, je nachdem, wie der Nutzer auf eine bestimmte Seite gelangt ist. Außerdem können Sie sich die vorherigen URLs oder deren Status ansehen.
Das ist mit der aktuellen History API nicht möglich.
Sie können auch auf ein "dispose"
-Ereignis für einzelne NavigationHistoryEntry
s warten, das ausgelöst wird, wenn der Eintrag nicht mehr Teil des Browserverlaufs ist. Das kann im Rahmen einer allgemeinen Bereinigung, aber auch während der Navigation passieren. Wenn Sie beispielsweise 10 Orte zurückgehen und dann vorwärts navigieren, werden diese 10 Verlaufseinträge gelöscht.
Beispiele
Das "navigate"
-Ereignis wird, wie oben erwähnt, für alle Arten von Navigation ausgelöst.
Im Anhang der Spezifikation finden Sie eine lange Liste aller möglichen Typen.
Bei vielen Websites ist der häufigste Fall, dass der Nutzer auf ein <a href="...">
klickt. Es gibt jedoch zwei bemerkenswerte, komplexere Navigationstypen, die es wert sind, behandelt zu werden.
Programmatische Navigation
Die erste ist die programmatische Navigation, bei der die Navigation durch einen Methodenaufruf in Ihrem clientseitigen Code ausgelöst wird.
Sie können navigation.navigate('/another_page')
an einer beliebigen Stelle in Ihrem Code aufrufen, um eine Navigation auszulösen.
Dies wird vom zentralen Event-Listener verarbeitet, der für den "navigate"
-Listener registriert ist. Ihr zentraler Listener wird synchron aufgerufen.
Diese Methode soll eine verbesserte Aggregation älterer Methoden wie location.assign()
und ähnlicher Methoden sowie der Methoden pushState()
und replaceState()
der History API ermöglichen.
Die Methode navigation.navigate()
gibt ein Objekt zurück, das zwei Promise
-Instanzen in { committed, finished }
enthält.
So kann der Aufrufer warten, bis der Übergang entweder „committed“ (die sichtbare URL hat sich geändert und ein neues NavigationHistoryEntry
ist verfügbar) oder „finished“ (alle von intercept({ handler })
zurückgegebenen Promises sind abgeschlossen oder wurden aufgrund eines Fehlers oder einer anderen Navigation abgelehnt) ist.
Die Methode navigate
hat auch ein Optionenobjekt, in dem Sie Folgendes festlegen können:
state
: Der Status für den neuen Verlaufs-Eintrag, wie er über die Methode.getState()
fürNavigationHistoryEntry
verfügbar ist.history
: Kann auf"replace"
gesetzt werden, um den aktuellen Verlaufseintrag zu ersetzen.info
: Ein Objekt, das übernavigateEvent.info
an das Navigationsereignis übergeben werden soll.
info
kann beispielsweise verwendet werden, um eine bestimmte Animation anzugeben, die dazu führt, dass die nächste Seite angezeigt wird.
Alternativ können Sie eine globale Variable festlegen oder sie in den Hash einfügen. Beide Optionen sind etwas umständlich.)
Wichtig: Diese info
wird nicht noch einmal wiedergegeben, wenn ein Nutzer später eine Navigation auslöst, z.B. über die Schaltflächen „Zurück“ und „Vorwärts“.
Tatsächlich ist es in diesen Fällen immer undefined
.
navigation
bietet auch eine Reihe anderer Navigationsmethoden, die alle ein Objekt mit { committed, finished }
zurückgeben.
Ich habe bereits traverseTo()
(das ein key
akzeptiert, das einen bestimmten Eintrag im Verlauf des Nutzers angibt) und navigate()
erwähnt.
Es enthält auch back()
, forward()
und reload()
.
Diese Methoden werden wie navigate()
alle vom zentralen "navigate"
-Ereignis-Listener verarbeitet.
Eingereichte Formulare
Zweitens ist die HTML-<form>
-Übermittlung über POST eine spezielle Art der Navigation, die von der Navigation API abgefangen werden kann.
Obwohl eine zusätzliche Nutzlast enthalten ist, wird die Navigation weiterhin zentral vom "navigate"
-Listener verarbeitet.
Sie können die Formulareinsendung erkennen, indem Sie in NavigateEvent
nach der Property formData
suchen.
Hier ist ein Beispiel, in dem jede Formulareinsendung über fetch()
in eine Einsendung umgewandelt wird, die auf der aktuellen Seite bleibt:
navigation.addEventListener('navigate', navigateEvent => {
if (navigateEvent.formData && navigateEvent.canIntercept) {
// User submitted a POST form to a same-domain URL
// (If canIntercept is false, the event is just informative:
// you can't intercept this request, although you could
// likely still call .preventDefault() to stop it completely).
navigateEvent.intercept({
// Since we don't update the DOM in this navigation,
// don't allow focus or scrolling to reset:
focusReset: 'manual',
scroll: 'manual',
handler() {
await fetch(navigateEvent.destination.url, {
method: 'POST',
body: navigateEvent.formData,
});
// You could navigate again with {history: 'replace'} to change the URL here,
// which might indicate "done"
},
});
}
});
Was fehlt?
Trotz der zentralen Natur des "navigate"
-Event-Listeners wird "navigate"
gemäß der aktuellen Navigation API-Spezifikation nicht beim ersten Laden einer Seite ausgelöst.
Bei Websites, die serverseitiges Rendering (SSR) für alle Status verwenden, ist das in Ordnung. Ihr Server kann den richtigen Anfangsstatus zurückgeben. Das ist der schnellste Weg, um Inhalte für Ihre Nutzer bereitzustellen.
Bei Websites, auf denen clientseitiger Code zum Erstellen der Seiten verwendet wird, ist möglicherweise eine zusätzliche Funktion zum Initialisieren der Seite erforderlich.
Ein weiterer bewusster Designaspekt der Navigation API ist, dass sie nur in einem einzelnen Frame funktioniert, d. h. auf der Seite der obersten Ebene oder in einem einzelnen <iframe>
.
Das hat eine Reihe interessanter Auswirkungen, die in der Spezifikation genauer beschrieben werden. In der Praxis wird es jedoch die Verwirrung bei Entwicklern verringern.
Die bisherige History API hatte eine Reihe verwirrender Grenzfälle, z. B. die Unterstützung von Frames. Die neu konzipierte Navigation API behandelt diese Grenzfälle von Anfang an.
Schließlich gibt es noch keinen Konsens darüber, ob die Liste der Einträge, durch die der Nutzer navigiert hat, programmatisch geändert oder neu angeordnet werden soll. Dies wird derzeit diskutiert. Eine Option könnte sein, nur Löschungen zuzulassen: entweder historische Einträge oder „alle zukünftigen Einträge“. Letzteres würde einen temporären Status ermöglichen. Als Entwickler könnte ich beispielsweise:
- Dem Nutzer wird eine Frage gestellt, indem eine neue URL oder ein neuer Status aufgerufen wird.
- Nutzer können ihre Arbeit abschließen oder zurückgehen.
- einen Verlaufseintrag nach Abschluss einer Aufgabe entfernen
Das ist ideal für temporäre Modale oder Interstitials: Der Nutzer kann die neue URL mit der Zurück-Geste verlassen, aber nicht versehentlich wieder aufrufen, da der Eintrag entfernt wurde. Das ist mit der aktuellen History API nicht möglich.
Navigation API testen
Die Navigation API ist in Chrome 102 ohne Flags verfügbar. Sie können auch eine Demo von Domenic Denicola ausprobieren.
Die klassische History API scheint zwar einfach zu sein, ist aber nicht sehr gut definiert und weist eine große Anzahl von Problemen in Bezug auf Grenzfälle und die unterschiedliche Implementierung in verschiedenen Browsern auf. Wir hoffen, dass Sie uns Feedback zur neuen Navigation API geben.
Verweise
- WICG/navigation-api
- Mozilla Standards Position
- Intent To Prototype
- TAG-Überprüfung
- Chromestatus-Eintrag
Danksagungen
Vielen Dank an Thomas Steiner, Domenic Denicola und Nate Chapin für die Überprüfung dieses Beitrags.