Ujednolicenie przekierowywania po stronie klienta za pomocą zupełnie nowego interfejsu API, który całkowicie zmienia sposób tworzenia aplikacji jednostronicowych.
Aplikacje jednostronicowe (SPA) są zdefiniowane przez główną funkcję: dynamiczne przepisywanie treści w miarę interakcji użytkownika z witryną zamiast domyślnej metody wczytywania zupełnie nowych stron z serwera.
Chociaż aplikacje SPA mogły udostępniać tę funkcję za pomocą interfejsu History API (lub w ograniczonych przypadkach przez dostosowanie części #hash witryny), to jest to nieporędny interfejs API, który został opracowany na długo przed tym, zanim aplikacje SPA stały się normą. Internet potrzebuje zupełnie nowego podejścia. Interfejs Navigation API to proponowany interfejs API, który całkowicie zmienia tę przestrzeń, zamiast próbować po prostu naprawić błędy interfejsu History API. (np. Scroll Restoration naprawił interfejs History API, zamiast próbować go na nowo).
W tym artykule ogólnie omawiamy interfejs API Nawigacji. Aby zapoznać się z propozycją techniczną, przeczytaj raport Draft Report w repozytorium WICG.
Przykład użycia
Aby korzystać z interfejsu API Nawigacja, zacznij od dodania odbiornika "navigate"
do obiektu globalnego navigation
.
To zdarzenie jest centralne: będzie się uruchamiać w przypadku wszystkich typów nawigacji, niezależnie od tego, czy użytkownik wykonał jakieś działanie (np.kliknął link, przesłał formularz lub przewinął stronę w górę lub w dół), czy też nawigacja została wywołana programowo (np. za pomocą kodu witryny).
W większości przypadków pozwala on Twojemu kodowi zastąpić domyślne działanie przeglądarki w przypadku danego działania.
W przypadku aplikacji SPA oznacza to prawdopodobnie pozostawienie użytkownika na tej samej stronie i wczytanie lub zmianę treści witryny.
Do odbiornika "navigate"
przekazywany jest obiekt NavigateEvent
, który zawiera informacje o nawigacji, np. adres URL miejsca docelowego. Umożliwia to reagowanie na nawigację w jednym scentralizowanym miejscu.
Podstawowy odbiorca "navigate"
może wyglądać tak:
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});
}
});
Możesz poruszać się po stronie na 2 sposoby:
- wywołanie funkcji
intercept({ handler })
(jak opisano powyżej) w celu obsługi nawigacji; - wywołanie
preventDefault()
, co może spowodować całkowite anulowanie nawigacji;
W tym przykładzie wywołuje on funkcję intercept()
.
Przeglądarka wywołuje funkcję handler
, która powinna skonfigurować następny stan witryny.
Spowoduje to utworzenie obiektu przejścia navigation.transition
, którego inny kod może użyć do śledzenia postępu nawigacji.
Zarówno intercept()
, jak i preventDefault()
są zwykle dozwolone, ale są przypadki, w których nie można ich wywołać.
Nie można obsługiwać nawigacji za pomocą intercept()
, jeśli nawigacja odbywa się między domenami.
Nie możesz też anulować nawigacji za pomocą preventDefault()
, jeśli użytkownik naciska w przeglądarce przyciski Wstecz lub Wprzód. Nie możesz też zablokować użytkowników w Twojej witrynie.
(temat jest omawiany na GitHubie).
Nawet jeśli nie możesz zatrzymać ani przechwycić nawigacji, zdarzenie "navigate"
zostanie uruchomione.
Jest to informacja, więc Twój kod może np. rejestrować zdarzenie Analytics, aby wskazać, że użytkownik opuszcza Twoją witrynę.
Dlaczego warto dodać kolejne zdarzenie do platformy?
Detektor zdarzeń "navigate"
centralizuje obsługę zmian adresów URL w aplikacji SPA.
Jest to trudna propozycja, która korzysta ze starszych interfejsów API.
Jeśli masz już zaimplementowane przekierowywanie w ramach własnej aplikacji SPA za pomocą interfejsu History API, możesz mieć dodany kod podobny do tego:
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));
To w porządku, ale nie jest wyczerpujące. Linki mogą pojawiać się i znikać ze strony, a nie są jedynym sposobem nawigacji po stronach. Może to być np. wypełnienie formularza lub użycie mapy obrazkowej. Twoja strona może obsługiwać te funkcje, ale istnieje wiele możliwości, które można uprościć. Nowy interfejs Navigation API właśnie to umożliwia.
Ponadto nie obsługuje ona przewijania wstecz i do przodu. Jest jeszcze inne wydarzenie, "popstate"
.
Osobiście uważam, że interfejs History API może w pewnym stopniu pomóc w realizacji tych możliwości.
W rzeczywistości ma ona tylko 2 obszary powierzchni: reaguje, gdy użytkownik naciśnie w przeglądarce przyciski Wstecz lub Do przodu, a także gdy przesyła i zastępuje adresy URL. Nie ma analogii do "navigate"
, chyba że ręcznie skonfigurujesz odsłuchiwanie zdarzeń kliknięcia, jak pokazano powyżej.
Wybieranie sposobu obsługi nawigacji
Plik navigateEvent
zawiera wiele informacji na temat nawigacji, które pomogą Ci określić, jak sobie poradzić z konkretną nawigacją.
Najważniejsze właściwości to:
canIntercept
- Jeśli ta wartość jest fałszem, nie możesz przechwycić nawigacji. Przechodzenie między domenami i przechodzenie między dokumentami nie może być przechwytywane.
destination.url
- Prawdopodobnie najważniejsza informacja, którą należy wziąć pod uwagę podczas obsługi nawigacji.
hashChange
- Wartość Prawda, jeśli nawigacja jest w tym samym dokumencie, a podpis cyfrowy jest jedyną częścią adresu URL, która różni się od bieżącego adresu URL.
W nowoczesnych SPA skrót powinien służyć do łączenia różnych części bieżącego dokumentu. Jeśli więc
hashChange
ma wartość true, prawdopodobnie nie musisz przechwytywać tej nawigacji. downloadRequest
- Jeśli ta wartość jest równa True, oznacza to, że nawigacja została zainicjowana przez link z atrybutem
download
. W większości przypadków nie musisz ich przechwytywać. formData
- Jeśli to pole nie jest puste, oznacza, że ta nawigacja jest częścią przesyłania formularza POST.
Pamiętaj o tym, gdy zarządzasz nawigacją.
Jeśli chcesz obsługiwać tylko nawigacje GET, nie przechwytuj nawigacji, w których
formData
nie jest równe null. Przykład obsługi przesłań formularza znajdziesz dalej w tym artykule. navigationType
- Jedno z tych wartości:
"reload"
,"push"
,"replace"
lub"traverse"
. Jeśli jest to"traverse"
, nawigacji nie można anulować za pomocąpreventDefault()
.
Na przykład funkcja shouldNotIntercept
użyta w pierwszym przykładzie może wyglądać tak:
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
);
}
Przechwytywanie
Gdy Twój kod wywołuje funkcję intercept({ handler })
z poziomu swojego odbiornika "navigate"
, informuje przeglądarkę, że przygotowuje stronę do nowego, zaktualizowanego stanu i że nawigacja może trochę potrwać.
Najpierw przeglądarka rejestruje pozycję przewijania dla bieżącego stanu, aby można było ją opcjonalnie przywrócić później, a potem wywołuje funkcję handler
.
Jeśli funkcja handler
zwraca obietnicę (co dzieje się automatycznie w przypadku funkcji asynchronicznych), ta obietnica informuje przeglądarkę, ile czasu zajmuje nawigacja i czy została ona zakończona sukcesem.
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);
},
});
}
});
W ramach tego interfejsu API wprowadzamy pojęcie semantyczne, które rozumie przeglądarka: w ramach nawigacji SPA w ciągu czasu zmienia się adres URL dokumentu i jego stan. Ma to wiele potencjalnych zalet, w tym ułatwienia dostępu: przeglądarki mogą wyświetlać początek, koniec lub potencjalną awarię nawigacji. Na przykład Chrome aktywuje swój natywny wskaźnik wczytywania i pozwala użytkownikowi na interakcję z przyciskiem Zatrzymaj. (Obecnie nie występuje to, gdy użytkownik przechodzi za pomocą przycisków Wstecz i Dalej, ale wkrótce to naprawimy).
Przypisywanie nawigacji
Podczas przechwytywania nawigacji nowy adres URL zacznie obowiązywać tuż przed wywołaniem wywołania zwrotnego handler
.
Jeśli nie zaktualizujesz DOM-u od razu, przez pewien czas będzie wyświetlana stara treść wraz z nowym adresem URL.
Ma to wpływ na takie kwestie jak rozwiązywanie adresów URL bezwzględnych podczas pobierania danych lub wczytywania nowych zasobów podrzędnych.
Sposób opóźnienia zmiany adresu URL jest omawiany na GitHubie, ale ogólnie zalecamy natychmiastowe zaktualizowanie strony za pomocą jakiegoś zastępnika dla nadchodzących treści:
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);
},
});
}
});
Pozwala to uniknąć problemów z rozpatrywaniem adresów URL, a także sprawia, że działanie wydaje się szybkie, ponieważ użytkownik otrzymuje natychmiastową odpowiedź.
Przerywanie sygnałów
Ponieważ w obiekcie intercept()
możesz wykonywać zadania asynchroniczne, nawigacja może stać się zbędna.
Dzieje się tak, gdy:
- Użytkownik klika inny link lub kod powoduje inną nawigację. W tym przypadku stara nawigacja zostaje zastąpiona nową.
- Użytkownik klika przycisk „Stop” w przeglądarce.
Aby uwzględnić wszystkie te możliwości, zdarzenie przekazywane do odbiornika "navigate"
zawiera właściwość signal
, która jest obiektem AbortSignal
.
Więcej informacji znajdziesz w sekcji Anulowanie pobierania.
Krótko mówiąc, jest to obiekt, który uruchamia zdarzenie, gdy należy przerwać pracę.
W szczególności możesz przekazać AbortSignal
do wszystkich wywołań do fetch()
, co spowoduje anulowanie żądań sieci w trakcie ich przesyłania, jeśli nawigacja zostanie zablokowana.
Pozwoli to zaoszczędzić przepustowość użytkownika i odrzucić wartość Promise
zwracaną przez fetch()
, uniemożliwiając dalszemu kodowi wykonywanie takich działań jak aktualizowanie interfejsu DOM w celu wyświetlenia nieprawidłowej nawigacji po stronie.
Oto poprzedni przykład, ale z użyciem funkcji getArticleContent
, który pokazuje, jak można używać funkcji AbortSignal
z funkcją fetch()
:
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);
},
});
}
});
Obsługa przewijania
Gdy intercept()
nawigację, przeglądarka automatycznie spróbuje przewinąć stronę.
W przypadku przechodzenia do nowego wpisu w historii (gdy navigationEvent.navigationType
to "push"
lub "replace"
) oznacza to próbę przewinięcia do części wskazanej przez fragment adresu URL (część po #
) lub zresetowanie przewijania do góry strony.
W przypadku ponownego wczytania i przechodzenia oznacza to przywrócenie pozycji przewijania do miejsca, w którym ostatnio wyświetlano ten wpis historii.
Domyślnie dzieje się to po spełnieniu obietnicy zwracanej przez handler
, ale jeśli chcesz przewinąć wcześniej, możesz wywołać navigateEvent.scroll()
:
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);
},
});
}
});
Możesz też całkowicie wyłączyć automatyczne przewijanie, ustawiając opcję scroll
w intercept()
na "manual"
:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
Obsługa ostrości
Gdy obietnica zwracana przez Twoją funkcję handler
zostanie spełniona, przeglądarka skoncentruje się na pierwszym elemencie z ustawionym atrybutem autofocus
lub na elemencie <body>
, jeśli żaden element nie ma tego atrybutu.
Możesz zrezygnować z tego zachowania, ustawiając opcję focusReset
w sekcji intercept()
na "manual"
:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
Zdarzenia powodzenia i niepowodzenia
Gdy wywołasz metodę obsługi intercept()
, może się zdarzyć jedno z tych 2 wydarzeń:
- Jeśli zwrócony element
Promise
spełnia kryteria (lub nie wywołano metodyintercept()
), interfejs Navigation API wywoła element"navigatesuccess"
z wartościąEvent
. - Jeśli zwrócona wartość
Promise
jest odrzucana, interfejs API wywoła"navigateerror"
z wartościąErrorEvent
.
Te zdarzenia umożliwiają scentralizowane przetwarzanie przez kod informacji o powodzeniu lub niepowodzeniu. Możesz na przykład ukryć wyświetlany wcześniej wskaźnik postępu:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
Możesz też wyświetlić komunikat o błędzie:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
Detektor zdarzeń "navigateerror"
, który otrzymuje wartość ErrorEvent
, jest szczególnie przydatny, ponieważ zawsze odbiera błędy z kodu konfigurującego nową stronę.
Możesz po prostu await fetch()
, wiedząc, że jeśli sieć jest niedostępna, błąd zostanie ostatecznie przekierowany do "navigateerror"
.
Wpisy nawigacji
navigation.currentEntry
zapewnia dostęp do bieżącego wpisu.
Jest to obiekt opisujący, gdzie znajduje się użytkownik.
Ten wpis zawiera bieżący adres URL, metadane, które można wykorzystać do identyfikacji tego wpisu w ciągu czasu, oraz stan podany przez dewelopera.
Metadane obejmują key
, unikalną właściwość ciągu znaków każdego wpisu, która reprezentuje bieżący wpis i jego miejsce.
Klucz pozostaje taki sam, nawet jeśli adres URL lub stan bieżącego wpisu ulegnie zmianie.
Nadal jest w tym samym gnieździe.
Jeśli natomiast użytkownik naciśnie Wstecz, a następnie ponownie otworzy tę samą stronę, wartość key
się zmieni, ponieważ nowy wpis utworzy nowy slot.
Dla dewelopera key
jest przydatna, ponieważ interfejs Navigation API umożliwia przekierowanie użytkownika bezpośrednio do wpisu o odpowiednim kluczu.
Możesz go trzymać, nawet w stanach innych wpisów, aby łatwo przełączać się między stronami.
// 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;
Stan
Interfejs API Nawigacji udostępnia pojęcie „stanu”, czyli informacji dostarczonych przez dewelopera, które są przechowywane na stałe w bieżącym wpisie historii, ale nie są bezpośrednio widoczne dla użytkownika.
Jest to bardzo podobne, ale ulepszone podejście do history.state
w History API.
W interfejsie API nawigacji możesz wywołać metodę .getState()
bieżącego wpisu (lub dowolnego wpisu), aby zwrócić kopię jego stanu:
console.log(navigation.currentEntry.getState());
Domyślnie będzie to undefined
.
Stan ustawień
Chociaż obiekty stanu mogą ulegać zmianom, te zmiany nie są zapisywane w historii, dlatego:
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
Prawidłowy sposób ustawiania stanu to nawigator skryptu:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
Gdzie newState
może być dowolny obiekt możliwy do sklonowania.
Jeśli chcesz zaktualizować stan bieżącego wpisu, najlepiej wykonać nawigację, która zastąpi bieżący wpis:
navigation.navigate(location.href, {state: newState, history: 'replace'});
Następnie "navigate"
może odebrać tę zmianę za pomocą navigateEvent.destination
:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
Synchroniczna aktualizacja stanu
Zazwyczaj lepiej jest zaktualizować stan asynchronicznie za pomocą navigation.reload({state: newState})
, a następnie odbiorca "navigate"
może zastosować ten stan. Czasami jednak zmiana stanu jest już w pełni zastosowana, gdy Twój kod ją wykryje, np. gdy użytkownik przełączy element <details>
lub zmieni stan pola formularza. W takich przypadkach warto zaktualizować stan, aby zmiany były zachowane podczas ponownego wczytywania i przechodzenia. Można to zrobić za pomocą updateCurrentEntry()
:
navigation.updateCurrentEntry({state: newState});
Aby dowiedzieć się więcej o tej zmianie, weź udział w wydarzeniu:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
Jeśli jednak zauważysz, że reagujesz na zmiany stanu w funkcji "currententrychange"
, możesz dzielić lub nawet powielać kod obsługi stanu między zdarzeniem "navigate"
a zdarzeniem "currententrychange"
, podczas gdy funkcja navigation.reload({state: newState})
pozwoli Ci obsłużyć to w jednym miejscu.
Parametry stanu a parametry adresu URL
Stan może być obiektem ustrukturyzowanym, więc łatwo jest go używać do wszystkich stanów aplikacji. W wielu przypadkach lepiej jednak przechowywać stan w adresie URL.
Jeśli chcesz, aby stan został zachowany, gdy użytkownik udostępni adres URL innemu użytkownikowi, zapisz go w adresie URL. W przeciwnym razie obiekt state jest lepszym rozwiązaniem.
Dostęp do wszystkich wpisów
„Obecny wpis” to jednak nie wszystko.
Interfejs API umożliwia też dostęp do całej listy wpisów, po których użytkownik się poruszał podczas korzystania z Twojej witryny. Aby uzyskać tablicę wpisów w postaci migawki, wywołaj metodę navigation.entries()
.
Można go wykorzystać np. do wyświetlania innego interfejsu w zależności od tego, jak użytkownik dotarł do danej strony, lub do sprawdzania poprzednich adresów URL i ich stanów.
Nie jest to możliwe w przypadku obecnego interfejsu History API.
Możesz też nasłuchiwać zdarzenia "dispose"
w poszczególnych NavigationHistoryEntry
, które jest wywoływane, gdy wpis nie jest już częścią historii przeglądarki. Może się to zdarzyć w ramach ogólnego czyszczenia, ale też podczas nawigacji. Jeśli na przykład przejdziesz 10 miejsc w tył, a potem w przód, 10 ostatnich wpisów w historii zostanie usuniętych.
Przykłady
Zdarzenie "navigate"
jest wywoływane w przypadku wszystkich typów nawigacji, jak wspomniano powyżej.
(w specyfikacji jest długi aneks ze wszystkimi możliwymi typami).
W przypadku wielu witryn najczęstszym przypadkiem jest kliknięcie przez użytkownika przycisku <a href="...">
, ale istnieją 2 warte uwagi bardziej złożone typy nawigacji.
Nawigacja zautomatyzowana
Pierwsza to nawigacja programowa, która jest wywoływana przez metodę w kodzie po stronie klienta.
Aby wywołać nawigację, możesz zadzwonić pod numer navigation.navigate('/another_page')
z dowolnego miejsca w kodeksie.
Zajmie się tym scentralizowany detektor zdarzeń zarejestrowany w detektorze "navigate"
, a scentralizowany detektor zostanie wywołany synchronicznie.
Jest to ulepszona metoda agregacji starszych metod, takich jak location.assign()
i friends, oraz metod interfejsu History API pushState()
i replaceState()
.
Metoda navigation.navigate()
zwraca obiekt, który zawiera 2 wystąpienia Promise
w obiekcie { committed, finished }
.
Dzięki temu wywołujący może czekać, aż przejście zostanie „zaakceptowane” (czyli widoczny adres URL zmieni się i będzie dostępny nowy adres NavigationHistoryEntry
) lub „zakończone” (czyli wszystkie obietnice zwracane przez intercept({ handler })
są kompletne lub odrzucone z powodu błędu lub wyprzedzenia przez inną nawigację).
Metoda navigate
ma też obiekt opcji, w którym możesz ustawić:
state
: stan nowego wpisu w historii, dostępny za pomocą metody.getState()
w obiekcieNavigationHistoryEntry
.history
: można go ustawić na"replace"
, aby zastąpić bieżący wpis w historii.info
: obiekt do przekazania zdarzeniu nawigacji za pomocą parametrunavigateEvent.info
.
W szczególności info
może być przydatna np. do oznaczenia animacji, która powoduje wyświetlenie następnej strony.
(Możesz też ustawić zmienną globalną lub uwzględnić ją w ramach #hash. Obie opcje są nieco niewygodne.
Warto pamiętać, że info
nie będzie odtwarzany, jeśli użytkownik przejdzie do innej strony, np. za pomocą przycisków Wstecz i Dalej.
W takich przypadkach zawsze będzie to undefined
.
navigation
ma też kilka innych metod nawigacji, które zwracają obiekt zawierający { committed, finished }
.
Wspomnałem już o funkcji traverseTo()
(która akceptuje parametr key
oznaczający konkretny wpis w historii użytkownika) oraz o funkcji navigate()
.
Obejmuje też back()
, forward()
i reload()
.
Wszystkie te metody są obsługiwane – podobnie jak navigate()
– przez scentralizowany detektor zdarzeń "navigate"
.
Przesłane formularze
Po drugie, przesyłanie HTML <form>
za pomocą metody POST to specjalny typ nawigacji, który może przechwycić interfejs Navigation API.
Chociaż zawiera dodatkowy ładunek, nawigacja jest nadal zarządzana centralnie przez "navigate"
.
Przesłanie formularza można wykryć, szukając w uzyskanym NavigateEvent
właściwości formData
.
Oto przykład, w którym dowolne przesłanie formularza za pomocą tagu fetch()
staje się przesyłaniem formularza, który pozostaje na bieżącej stronie:
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"
},
});
}
});
Czego brakuje?
Pomimo scentralizowanego charakteru odbiornika zdarzeń "navigate"
obecna specyfikacja interfejsu Navigation API nie powoduje wywołania "navigate"
przy pierwszym wczytaniu strony.
W przypadku witryn, które używają renderowania po stronie serwera (SSR) we wszystkich stanach, może to być w porządku – serwer może zwrócić prawidłowy stan początkowy, co jest najszybszym sposobem na dostarczenie treści użytkownikom.
Jednak w przypadku witryn, które do tworzenia stron używają kodu po stronie klienta, może być konieczne utworzenie dodatkowej funkcji do inicjowania strony.
Kolejnym celowym wyborem w przypadku interfejsu Navigation API jest to, że działa on tylko w ramach jednego elementu – strony najwyższego poziomu lub pojedynczego <iframe>
.
Ma to kilka ciekawych konsekwencji, które są szczegółowo opisane w specyfikacji, ale w praktyce zmniejszy to dezorientację deweloperów.
W poprzedniej wersji interfejsu History API występowało kilka mylących przypadków skrajnych, takich jak obsługa ramek. Zrewidowany interfejs Navigation API obsługuje te przypadki skrajne od samego początku.
Nie ma jeszcze konsensusu co do programowego modyfikowania ani przestawiania listy pozycji, po których użytkownik się poruszał. Ta kwestia jest obecnie analizowana, ale jedną z opcji jest zezwolenie na usuwanie tylko wpisów historycznych lub „wszystkich przyszłych wpisów”. Ta druga opcja pozwoliłaby na tymczasowy stan. Jako programista mogę na przykład:
- zadać użytkownikowi pytanie, przechodząc do nowego adresu URL lub stanu;
- zezwala użytkownikowi na dokończenie pracy (lub powrót do poprzedniego ekranu).
- usunąć pozycję z historii po ukończeniu zadania.
Może to być idealne rozwiązanie w przypadku tymczasowych modali lub reklam przejściowych: nowy adres URL może być zamknięty za pomocą gestów, ale użytkownik nie może przypadkowo przejść do następnej strony, aby otworzyć go ponownie (ponieważ wpis został usunięty). Nie jest to możliwe w ramach obecnego interfejsu History API.
Wypróbuj interfejs Navigation API
Interfejs Navigation API jest dostępny w Chrome 102 bez flag. Możesz też wypróbować wersję demonstracyjną przygotowaną przez Domenica Denicola.
Chociaż klasyczne History API wydaje się proste, nie jest ono dobrze zdefiniowane i ma wiele problemów związanych z rzadkimi przypadkami oraz z sposobem jego implementacji w różnych przeglądarkach. Mamy nadzieję, że podzielisz się z nami opinią na temat nowego interfejsu Navigation API.
Pliki referencyjne
- WICG/navigation-api
- Mozilla Standards Position
- Intencja tworzenia prototypu
- Sprawdzanie tagów
- Wpis w Chromestatus
Podziękowania
Dziękujemy Thomasowi Steinerowi, Domenicowi Denicola i Nate’owi Chapinowi za sprawdzenie tego posta.