Routage côté client moderne: API Navigation

Standardisation du routage côté client via une toute nouvelle API qui remanie complètement la création d'applications monopages.

Jake Archibld
Jake Archibald

Navigateurs pris en charge

  • 102
  • 102
  • x
  • x

Source

Les applications monopages (SPA) sont définies par une fonctionnalité essentielle: la réécriture dynamique de leur contenu en fonction de l'interaction de l'utilisateur avec le site, au lieu de charger des pages entièrement nouvelles depuis le serveur, par défaut.

Même si les applications monopages ont été en mesure de vous proposer cette fonctionnalité via l'API History (ou, dans de rares cas, en ajustant la partie #hachage du site), il s'agit d'une API peu intuitive développée bien avant qu'elles ne soient la norme. Le Web réclame une toute nouvelle approche. L'API Navigation est une proposition d'API qui remanie complètement cet espace, au lieu d'essayer de simplement corriger les bords approximatifs de l'API History. (Par exemple, Scroll Restoration a appliqué un correctif à l'API History au lieu d'essayer de la réinventer.)

Cet article décrit l'API Navigation dans les grandes lignes. Si vous souhaitez lire la proposition technique, consultez le brouillon du rapport dans le référentiel WICG.

Exemple d'utilisation

Pour utiliser l'API Navigation, commencez par ajouter un écouteur "navigate" sur l'objet navigation global. Fondamentalement, cet événement est centralisé: il se déclenche pour tous les types de navigations, que l'utilisateur ait effectué une action (comme cliquer sur un lien, envoyer un formulaire ou revenir en arrière ou avancer) ou lorsque la navigation est déclenchée par programmation (via le code de votre site). Dans la plupart des cas, elle permet à votre code de remplacer le comportement par défaut du navigateur pour cette action. Pour les applications monopages, cela implique probablement de maintenir l'utilisateur sur la même page et de charger ou modifier le contenu du site.

Un NavigateEvent est transmis à l'écouteur "navigate" qui contient des informations sur la navigation, telles que l'URL de destination, et vous permet de répondre à la navigation dans un emplacement centralisé. Un écouteur "navigate" de base pourrait se présenter comme suit:

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});
  }
});

Vous pouvez gérer la navigation de deux manières:

  • Appel de intercept({ handler }) (comme décrit ci-dessus) pour gérer la navigation
  • Appel de preventDefault() (ce qui peut annuler complètement la navigation)

Cet exemple appelle intercept() sur l'événement. Le navigateur appelle votre rappel handler, qui devrait configurer l'état suivant de votre site. Cette opération crée un objet de transition, navigation.transition, dont un autre code peut utiliser pour suivre la progression de la navigation.

intercept() et preventDefault() sont généralement autorisés, mais dans certains cas, ils ne peuvent pas être appelés. Vous ne pouvez pas gérer les navigations via intercept() s'il s'agit d'une navigation multi-origine. De plus, vous ne pouvez pas annuler une navigation via preventDefault() si l'utilisateur appuie sur le bouton "Précédent" ou "Suivant" de son navigateur. Vous ne devriez pas pouvoir piéger vos visiteurs sur votre site. (Ce point est en cours de discussion sur GitHub.)

Même si vous ne pouvez pas arrêter ou intercepter la navigation elle-même, l'événement "navigate" se déclenche quand même. Elle est informative. Votre code peut, par exemple, enregistrer un événement Analytics pour indiquer qu'un utilisateur quitte votre site.

Pourquoi ajouter un autre événement à la plate-forme ?

Un écouteur d'événements "navigate" centralise la gestion des modifications d'URL dans une application monopage. Cette proposition est difficile à utiliser avec d'anciennes API. Si vous avez déjà écrit le routage de votre propre SPA à l'aide de l'API History, vous avez peut-être ajouté du code comme celui-ci:

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));

C'est bien, mais pas exhaustif. Les liens peuvent entrer et sortir de votre page. Ils ne sont pas le seul moyen pour les utilisateurs de naviguer dans les pages. Par exemple, ils peuvent envoyer un formulaire ou même utiliser une image cliquable. Il se peut que votre page traite de ces problèmes, mais il existe une longue liste de possibilités qui pourrait être simplifiée, ce qui est un avantage de la nouvelle API Navigation.

De plus, ce qui précède ne gère pas la navigation avant/arrière. Il y a un autre événement pour cela, "popstate".

Personnellement, l'API History semble souvent aider à exploiter ces possibilités. Cependant, il ne comporte en réalité que deux zones de surface: répondre si l'utilisateur appuie sur "Retour" ou "Suivant" dans son navigateur, et transmettre et remplacer des URL. Il n'a aucune analogie avec "navigate", sauf si vous configurez manuellement des écouteurs pour les événements de clic, comme illustré ci-dessus, par exemple.

Décider comment gérer une navigation

navigateEvent contient de nombreuses informations sur la navigation que vous pouvez utiliser pour décider comment gérer une navigation particulière.

Les principales propriétés sont les suivantes:

canIntercept
Si cette valeur est "false", vous ne pouvez pas intercepter la navigation. Les navigations multi-origines et les balayages de plusieurs documents ne peuvent pas être interceptées.
destination.url
C'est probablement l'information la plus importante à prendre en compte pour gérer la navigation.
hashChange
"True" si la navigation porte sur le même document et que le hachage est la seule partie de l'URL différente de l'URL actuelle. Dans les applications Web monopages modernes, le hachage doit servir à créer des liens vers différentes parties du document actuel. Ainsi, si hashChange est "true", vous n'aurez probablement pas besoin d'intercepter cette navigation.
downloadRequest
Si la valeur est "true", la navigation a été lancée par un lien avec un attribut download. Dans la plupart des cas, vous n'avez pas besoin d'intercepter cette information.
formData
Si la valeur n'est pas nulle, cela signifie que ces éléments de navigation font partie d'un envoi de formulaire POST. Tenez-en compte lorsque vous gérez la navigation. Si vous souhaitez ne gérer que les navigations GET, évitez d'intercepter les navigations où formData n'est pas nul. Consultez l'exemple sur la gestion des envois de formulaires plus loin dans cet article.
navigationType
Il s'agit d'un élément "reload", "push", "replace" ou "traverse". S'il a la valeur "traverse", cette navigation ne peut pas être annulée via preventDefault().

Par exemple, la fonction shouldNotIntercept utilisée dans le premier exemple pourrait ressembler à ceci:

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
  );
}

Interception

Lorsque votre code appelle intercept({ handler }) à partir de son écouteur "navigate", il informe le navigateur qu'il prépare la page pour le nouvel état mis à jour, et que la navigation peut prendre un certain temps.

Le navigateur commence par capturer la position de défilement de l'état actuel afin qu'il puisse être restauré ultérieurement si vous le souhaitez, puis il appelle votre rappel handler. Si votre handler renvoie une promesse (ce qui se produit automatiquement avec les async functions), cette promesse indique au navigateur la durée de la navigation et si elle aboutit.

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);
      },
    });
  }
});

Cette API introduit donc un concept sémantique que le navigateur comprend: une navigation SPA se produit au fil du temps, ce qui fait passer le document d'une URL et d'un état précédents à un nouvel état. Cela présente de nombreux avantages potentiels, y compris l'accessibilité: les navigateurs peuvent afficher le début ou la fin d'une navigation, ou les échecs potentiels. Chrome, par exemple, active son indicateur de chargement natif et permet à l'utilisateur d'interagir avec le bouton d'arrêt. (Ce problème ne se produit pas actuellement lorsque l'utilisateur navigue via les boutons "Précédent/Suivant", mais ce problème sera bientôt résolu.)

Lors de l'interception de navigations, la nouvelle URL prend effet juste avant l'appel de votre rappel handler. Si vous ne mettez pas à jour le DOM immédiatement, l'ancien contenu s'affichera avec la nouvelle URL. Cela a un impact sur des éléments tels que la résolution des URL relatives lors de la récupération de données ou du chargement de nouvelles sous-ressources.

On discute sur GitHub pour retarder le changement d'URL, mais il est généralement recommandé de mettre immédiatement à jour la page avec une sorte d'espace réservé pour le contenu entrant:

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);
      },
    });
  }
});

Cela permet non seulement d'éviter les problèmes de résolution d'URL, mais également d'être rapide, car vous répondez instantanément à l'utilisateur.

Annuler les signaux

Étant donné que vous pouvez effectuer des tâches asynchrones dans un gestionnaire intercept(), il est possible que la navigation devienne redondante. Cela se produit dans les cas suivants:

  • L'utilisateur clique sur un autre lien ou un code effectue une autre navigation. Dans ce cas, l'ancienne navigation est abandonnée au profit de la nouvelle.
  • L'utilisateur clique sur le bouton "Arrêter" du navigateur.

Pour faire face à l'une de ces possibilités, l'événement transmis à l'écouteur "navigate" contient une propriété signal, qui est une AbortSignal. Pour en savoir plus, consultez la section Récupération abortable.

La version courte fournit essentiellement un objet qui déclenche un événement lorsque vous devez arrêter votre travail. Vous pouvez notamment transmettre un AbortSignal à tous les appels que vous passez à fetch(), ce qui annulera les requêtes réseau en cours si la navigation est préemptée. Cela permettra à la fois d'économiser la bande passante de l'utilisateur et de refuser le Promise renvoyé par fetch(), ce qui empêchera tout code suivant d'effectuer des actions telles que la mise à jour du DOM pour afficher une navigation sur les pages désormais non valide.

Voici l'exemple précédent, mais avec getArticleContent intégré, montrant comment AbortSignal peut être utilisé avec 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);
      },
    });
  }
});

Gestion du défilement

Lorsque vous intercept() une navigation, le navigateur tente de gérer le défilement automatiquement.

Pour les navigations vers une nouvelle entrée d'historique (lorsque navigationEvent.navigationType est défini sur "push" ou "replace"), cela signifie qu'il faut tenter de faire défiler la page jusqu'à la partie indiquée par le fragment d'URL (la partie située après #) ou rétablir le haut de la page.

Pour les actualisations et les balayages, cela signifie que la position de défilement est restaurée à la dernière fois que cette entrée d'historique a été affichée.

Par défaut, cela se produit une fois que la promesse renvoyée par votre handler est résolue, mais s'il est judicieux de faire défiler la page plus tôt, vous pouvez appeler 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);
      },
    });
  }
});

Vous pouvez également désactiver complètement la gestion automatique du défilement en définissant l'option scroll de intercept() sur "manual":

navigateEvent.intercept({
  scroll: 'manual',
  async handler() {
    // …
  },
});

Gestion de la mise au point

Une fois la promesse renvoyée par votre handler résolue, le navigateur sélectionne le premier élément avec l'attribut autofocus défini, ou l'élément <body> si aucun élément ne possède cet attribut.

Vous pouvez désactiver ce comportement en définissant l'option focusReset de intercept() sur "manual":

navigateEvent.intercept({
  focusReset: 'manual',
  async handler() {
    // …
  },
});

Événements de réussite et d'échec

Lorsque votre gestionnaire intercept() est appelé, deux cas de figure peuvent se produire:

  • Si l'Promise renvoyée aboutit (ou si vous n'avez pas appelé intercept()), l'API Navigation déclenche "navigatesuccess" avec un Event.
  • Si l'Promise renvoyée est refusée, l'API déclenche "navigateerror" avec un ErrorEvent.

Ces événements permettent à votre code de gérer la réussite ou l'échec de manière centralisée. Par exemple, vous pouvez gérer la réussite en masquant un indicateur de progression affiché précédemment, comme ceci:

navigation.addEventListener('navigatesuccess', event => {
  loadingIndicator.hidden = true;
});

Vous pouvez également afficher un message d'erreur en cas d'échec:

navigation.addEventListener('navigateerror', event => {
  loadingIndicator.hidden = true; // also hide indicator
  showMessage(`Failed to load page: ${event.message}`);
});

L'écouteur d'événements "navigateerror", qui reçoit un ErrorEvent, est particulièrement pratique, car il vous garantit de recevoir toutes les erreurs de votre code qui configure une nouvelle page. Vous pouvez simplement await fetch() en sachant que si le réseau n'est pas disponible, l'erreur finira par être acheminée vers "navigateerror".

navigation.currentEntry permet d'accéder à l'entrée actuelle. Il s'agit d'un objet qui décrit l'emplacement actuel de l'utilisateur. Cette entrée inclut l'URL actuelle, les métadonnées pouvant être utilisées pour identifier cette entrée au fil du temps et l'état fourni par le développeur.

Les métadonnées incluent key, une propriété de chaîne unique de chaque entrée qui représente l'entrée actuelle et son emplacement. Cette clé reste la même même si l'URL ou l'état de l'entrée actuelle change. Elle se trouve toujours au même emplacement. À l'inverse, si un utilisateur appuie sur "Retour", puis rouvre la même page, key change lorsque cette nouvelle entrée crée un espace.

Pour un développeur, key est utile, car l'API Navigation permet d'accéder directement à une entrée dont la clé correspond à l'utilisateur. Vous pouvez le conserver, même dans les états d'autres entrées, afin de passer facilement d'une page à l'autre.

// 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;

État

L'API Navigation affiche la notion d'"état", c'est-à-dire des informations fournies par le développeur qui sont stockées de manière persistante dans l'entrée d'historique actuelle, mais qui ne sont pas directement visibles par l'utilisateur. Cette fonctionnalité est très semblable à history.state dans l'API History, mais améliorée.

Dans l'API Navigation, vous pouvez appeler la méthode .getState() de l'entrée actuelle (ou de toute entrée) pour renvoyer une copie de son état:

console.log(navigation.currentEntry.getState());

Il s'agit par défaut de undefined.

État du paramètre

Bien que les objets d'état puissent être transformés, ces modifications ne sont pas enregistrées avec l'entrée d'historique. Par conséquent:

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

Pour définir l'état, il convient d'effectuer la navigation dans le script:

navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});

newState peut être n'importe quel objet clonable.

Si vous souhaitez mettre à jour l'état de l'entrée actuelle, il est préférable d'effectuer une navigation qui remplace l'entrée actuelle:

navigation.navigate(location.href, {state: newState, history: 'replace'});

Votre écouteur d'événements "navigate" peut ensuite récupérer cette modification via navigateEvent.destination:

navigation.addEventListener('navigate', navigateEvent => {
  console.log(navigateEvent.destination.getState());
});

Mettre à jour l'état de manière synchrone

En règle générale, il est préférable de mettre à jour l'état de manière asynchrone via navigation.reload({state: newState}). Votre écouteur "navigate" pourra alors appliquer cet état. Toutefois, il arrive que le changement d'état ait déjà été entièrement appliqué au moment où votre code en entend parler : par exemple, lorsque l'utilisateur active un élément <details> ou modifie l'état d'une saisie de formulaire. Dans ce cas, vous pouvez mettre à jour l'état afin que ces modifications soient conservées par le biais d'actualisations et de balayages. Ceci est possible en utilisant updateCurrentEntry():

navigation.updateCurrentEntry({state: newState});

Un événement est également organisé pour être informé de ce changement:

navigation.addEventListener('currententrychange', () => {
  console.log(navigation.currentEntry.getState());
});

Toutefois, si vous réagissez à des changements d'état dans "currententrychange", vous pouvez diviser, voire dupliquer votre code de gestion de l'état entre l'événement "navigate" et l'événement "currententrychange", tandis que navigation.reload({state: newState}) vous permettrait de le gérer au même endroit.

Paramètres d'état et d'URL

Étant donné que l'état peut être un objet structuré, il est tentant de l'utiliser pour tous les états de votre application. Toutefois, dans de nombreux cas, il est préférable de stocker cet état dans l'URL.

Si vous vous attendez à ce que l'état soit conservé lorsque l'utilisateur partage l'URL avec un autre utilisateur, stockez-le dans l'URL. Sinon, l'objet état est la meilleure option.

Accéder à toutes les entrées

L’« entrée actuelle » n’est toutefois pas la totalité. L'API permet également d'accéder à la liste complète des entrées par lesquelles un utilisateur a navigué sur votre site via son appel navigation.entries(), qui renvoie un tableau d'entrées sous forme d'instantané. Par exemple, elle peut permettre d'afficher une interface utilisateur différente en fonction de la façon dont l'utilisateur a accédé à une page spécifique, ou simplement de revenir aux URL précédentes ou à leurs états. Cela est impossible avec l'API History actuelle.

Vous pouvez également écouter un événement "dispose" sur des NavigationHistoryEntry individuels, qui est déclenché lorsque l'entrée ne fait plus partie de l'historique du navigateur. Cela peut se produire dans le cadre d'un nettoyage général, mais aussi lors de la navigation. Par exemple, si vous revenez en arrière de 10 lieux, puis que vous avancez, ces 10 entrées d'historique seront supprimées.

Exemples

L'événement "navigate" se déclenche pour tous les types de navigation, comme indiqué ci-dessus. (Il existe en fait une longue annexe dans la spécification de tous les types possibles.)

Bien que, pour de nombreux sites, le cas le plus courant soit lorsque l'utilisateur clique sur un <a href="...">, il convient d'examiner deux types de navigation notables et plus complexes.

Navigation programmatique

Tout d'abord, la navigation programmatique, qui est causée par un appel de méthode dans votre code côté client.

Vous pouvez appeler navigation.navigate('/another_page') à partir de n'importe quel point du code pour déclencher une navigation. Cette opération sera gérée par l'écouteur d'événements centralisé enregistré sur l'écouteur "navigate", et l'écouteur centralisé sera appelé de manière synchrone.

Il s'agit d'améliorer l'agrégation d'anciennes méthodes telles que location.assign() et les amis, ainsi que les méthodes pushState() et replaceState() de l'API History.

La méthode navigation.navigate() renvoie un objet qui contient deux instances Promise dans { committed, finished }. Cela permet au demandeur d'attendre que la transition soit "validée" (l'URL visible a changé et qu'un nouvel NavigationHistoryEntry est disponible) ou "terminée" (toutes les promesses renvoyées par intercept({ handler }) sont terminées ou refusées en raison d'un échec ou d'une préemption par une autre navigation).

La méthode navigate comporte également un objet d'options, dans lequel vous pouvez définir les éléments suivants:

  • state: état de la nouvelle entrée d'historique, tel que disponible via la méthode .getState() sur le NavigationHistoryEntry.
  • history: peut être défini sur "replace" pour remplacer l'entrée d'historique actuelle.
  • info: objet à transmettre à l'événement de navigation via navigateEvent.info.

En particulier, info peut être utile, par exemple, pour indiquer une animation particulière qui entraîne l'affichage de la page suivante. (L'alternative pourrait être de définir une variable globale ou de l'inclure dans le #hash. Les deux options sont un peu gênantes.) En particulier, ce info ne sera pas relancé si l'utilisateur la déclenche par la suite (via les boutons "Précédent" et "Suivant", par exemple). En réalité, la valeur est toujours undefined.

Démonstration de l'ouverture à partir de la gauche ou de la droite

navigation comporte également un certain nombre d'autres méthodes de navigation, qui renvoient toutes un objet contenant { committed, finished }. J'ai déjà mentionné traverseTo() (qui accepte un key qui indique une entrée spécifique de l'historique de l'utilisateur) et navigate(). Il inclut également back(), forward() et reload(). Ces méthodes sont toutes gérées, comme navigate(), par l'écouteur d'événements "navigate" centralisé.

Envois de formulaires

Deuxièmement, l'envoi de <form> HTML via POST est un type de navigation spécial, que l'API Navigation peut intercepter. Bien qu'elle inclue une charge utile supplémentaire, la navigation est toujours gérée de manière centralisée par l'écouteur "navigate".

Pour détecter l'envoi d'un formulaire, recherchez la propriété formData dans NavigateEvent. Voici un exemple qui transforme tout envoi de formulaire en un formulaire qui reste sur la page actuelle via fetch():

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"
      },
    });
  }
});

Quelles sont les informations manquantes ?

Malgré la nature centralisée de l'écouteur d'événements "navigate", la spécification actuelle de l'API Navigation ne déclenche pas "navigate" lors du premier chargement d'une page. Cela peut convenir pour les sites qui utilisent le rendu côté serveur pour tous les états. Votre serveur peut renvoyer l'état initial correct, ce qui constitue le moyen le plus rapide de présenter du contenu à vos utilisateurs. Toutefois, les sites qui exploitent le code côté client pour créer leurs pages devront peut-être créer une fonction supplémentaire pour initialiser ces pages.

Autre choix de conception intentionnel de l'API Navigation : elle ne fonctionne que dans un seul frame, c'est-à-dire la page de premier niveau ou un seul élément <iframe> spécifique. Cela présente un certain nombre d'implications intéressantes, décrites plus en détail dans la spécification. Cependant, dans la pratique, elles réduisent les risques de confusion pour les développeurs. L'API History précédente présente un certain nombre de cas particuliers qui prêtent à confusion, comme la prise en charge des frames, et la réinventée Navigation de l'API Navigation les gère dès le départ.

Enfin, il n'existe pas encore de consensus sur la modification ou la réorganisation programmatique de la liste des entrées parcourues par l'utilisateur. Ce sujet est en cours de discussion, mais vous pourriez n'autoriser que les suppressions: soit les entrées historiques, soit "toutes les entrées futures". Ce dernier autoriserait l'état temporaire. Par exemple, en tant que développeur, je pourrais:

  • Poser une question à l'utilisateur en accédant à une nouvelle URL ou à un nouvel état
  • permettre à l'utilisateur de terminer son travail (ou de revenir en arrière)
  • Supprimer une entrée d'historique à la fin d'une tâche

Cela peut être idéal pour les fenêtres modales ou les interstitiels temporaires: l'utilisateur peut quitter la nouvelle URL à l'aide du geste Retour, mais il ne peut pas avancer accidentellement pour l'ouvrir à nouveau (car l'entrée a été supprimée). Cela n'est tout simplement pas possible avec l'API History actuelle.

Essayer l'API Navigation

L'API Navigation est disponible sans indicateur dans Chrome 102. Vous pouvez également essayer la démo de Domenic Denicola.

Bien que l'API History classique semble simple, elle n'est pas très bien définie, et présente un grand nombre de problèmes liés aux cas particuliers et à la façon dont elle a été mise en œuvre différemment selon les navigateurs. N'hésitez pas à nous faire part de vos commentaires sur la nouvelle API Navigation.

Références

Remerciements

Merci à Thomas Steiner, Domenic Denicola et Nate Chapin pour leur avis sur ce post. Image principale issue du site Unsplash, de Jeremy Zero.