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.

Navigateurs pris en charge

  • Chrome: 102.
  • Edge: 102.
  • Firefox: non compatible.
  • Safari: non compatible.

Source

Les applications monopages sont définies par une caractéristique fondamentale : elles réécrivent dynamiquement leur contenu à mesure que l'utilisateur interagit avec le site, au lieu de charger de nouvelles pages complètes à partir du serveur, comme c'est la méthode par défaut.

Les SPA ont pu vous proposer cette fonctionnalité via l'API History (ou, dans certains cas limités, en ajustant la partie #hash du site). Il s'agit d'une API peu pratique développée bien avant que les SPA ne deviennent la norme. Le Web réclame une approche complètement nouvelle. L'API Navigation est une proposition d'API qui remanie complètement cet espace, au lieu d'essayer de corriger les défauts de l'API History. (Par exemple, Scroll Restoration a corrigé l'API History plutôt que d'essayer de la réinventer.)

Cet article décrit l'API Navigation dans les grandes lignes. Pour lire la proposition technique, consultez le rapport préliminaire dans le dépôt WICG.

Exemple d'utilisation

Pour utiliser l'API Navigation, commencez par ajouter un écouteur "navigate" à l'objet navigation global. Cet événement est fondamentalement 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 que la navigation soit déclenchée de manière programmatique (c'est-à-dire via le code de votre site). Dans la plupart des cas, il permet à votre code de remplacer le comportement par défaut du navigateur pour cette action. Pour les SPA, cela signifie probablement que l'utilisateur reste sur la même page et que le contenu du site est chargé ou modifié.

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 peut 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(), qui peut annuler complètement la navigation.

Cet exemple appelle intercept() sur l'événement. Le navigateur appelle votre rappel handler, qui doit configurer l'état suivant de votre site. Un objet de transition, navigation.transition, est alors créé, que d'autres codes peuvent utiliser pour suivre la progression de la navigation.

intercept() et preventDefault() sont généralement autorisés, mais il existe des cas où ils ne peuvent pas être appelés. Vous ne pouvez pas gérer les navigations via intercept() si la navigation est multidomaine. Vous ne pouvez pas non plus annuler une navigation via preventDefault() si l'utilisateur appuie sur les boutons "Précédent" ou "Suivant" dans son navigateur. Vous ne devez pas pouvoir piéger vos utilisateurs sur votre site. (Cette question 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. Il est informatif : votre code pourrait, 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 SPA. Il s'agit d'une proposition difficile à mettre en œuvre avec les 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 ce n'est pas exhaustif. Les liens peuvent apparaître et disparaître sur votre page, et ce ne sont pas les seuls moyens pour les utilisateurs de naviguer entre les pages. Par exemple, il peut envoyer un formulaire ou même utiliser une carte image. Votre page peut gérer ces éléments, mais il existe de nombreuses possibilités qui pourraient être simplifiées, ce que la nouvelle API Navigation permet de faire.

De plus, la navigation avant/arrière n'est pas gérée. Il existe un autre événement pour cela, "popstate".

Personnellement, l'API History me semble pouvoir contribuer à ces possibilités. Toutefois, il n'a en réalité que deux surfaces : répondre si l'utilisateur appuie sur "Retour" ou "Avant" dans son navigateur, et pousser et remplacer des URL. Il n'a aucune analogie avec "navigate", sauf si vous configurez manuellement des écouteurs pour les événements de clic, par exemple, comme indiqué ci-dessus.

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.

Voici les principales propriétés :

canIntercept
Si cette valeur est fausse, vous ne pouvez pas intercepter la navigation. Les navigations multi-origines et les balayages entre documents ne peuvent pas être interceptés.
destination.url
C'est probablement l'information la plus importante à prendre en compte lors de la gestion de la navigation.
hashChange
Présente la valeur "true" si la navigation concerne le même document et que le hachage est la seule partie de l'URL différente par rapport à l'URL actuelle. Dans les SPA modernes, le hachage doit servir à créer des liens vers différentes parties du document actuel. Par conséquent, si hashChange est défini sur "true", vous n'avez probablement pas besoin d'intercepter cette navigation.
downloadRequest
Si cette 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 requête.
formData
Si cette valeur n'est pas nulle, cette navigation fait partie d'un envoi de formulaire POST. Veillez à en tenir compte lorsque vous gérez la navigation. Si vous souhaitez gérer uniquement les navigations GET, évitez d'intercepter les navigations pour lesquelles formData n'est pas nul. Consultez l'exemple de gestion des envois de formulaires plus loin dans cet article.
navigationType
Il s'agit de "reload", "push", "replace" ou "traverse". Si la valeur est "traverse", cette navigation ne peut pas être annulée via preventDefault().

Par exemple, la fonction shouldNotIntercept utilisée dans le premier exemple pourrait être la suivante :

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 maintenant 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 pour l'état actuel afin de pouvoir être restauré ultérieurement si nécessaire, puis appelle votre rappel handler. Si votre handler renvoie une promesse (ce qui se produit automatiquement avec les fonctions asynchrones), 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);
      },
    });
  }
});

Par conséquent, cette API introduit un concept sémantique que le navigateur comprend : une navigation SPA est en cours, au fil du temps, et remplace l'URL et l'état précédents du document par un nouvel état. Cela présente un certain nombre d'avantages potentiels, y compris en termes d'accessibilité: les navigateurs peuvent afficher le début, la fin ou l'échec potentiel d'une navigation. Chrome, par exemple, active son indicateur de chargement natif et permet à l'utilisateur d'interagir avec le bouton d'arrêt. (Cela ne se produit pas actuellement lorsque l'utilisateur navigue à l'aide des boutons "Retour" et "Avant", mais cela sera bientôt corrigé.)

Lorsque vous interceptez des navigations, la nouvelle URL prend effet juste avant l'appel de votre rappel handler. Si vous ne mettez pas à jour le DOM immédiatement, une période est créée pendant laquelle l'ancien contenu s'affiche avec la nouvelle URL. Cela a un impact sur des éléments tels que la résolution d'URL relative lors de l'extraction de données ou du chargement de nouvelles sous-ressources.

Un moyen de retarder le changement d'URL est discuté sur GitHub, 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 évite non seulement les problèmes de résolution d'URL, mais aussi donne l'impression que la réponse est rapide, car vous répondez instantanément à l'utilisateur.

Signaux d'abandon

É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" dans le navigateur.

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

En résumé, il fournit essentiellement un objet qui déclenche un événement lorsque vous devez arrêter votre travail. Plus précisément, vous pouvez transmettre un AbortSignal à tous les appels que vous effectuez à fetch(), ce qui annulera les requêtes réseau en cours d'exécution si la navigation est préemptée. Cela permet d'économiser la bande passante de l'utilisateur et de rejeter le Promise renvoyé par fetch(), ce qui empêche tout code suivant d'effectuer des actions telles que la mise à jour du DOM pour afficher une navigation de page 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 "push" ou "replace"), cela signifie tenter de faire défiler la page jusqu'à la partie indiquée par le fragment d'URL (la partie située après #) ou de réinitialiser le défilement en haut de la page.

Pour les actualisations et les balayages, cela signifie restaurer la position de défilement à l'endroit où cette entrée d'historique était affichée pour la dernière fois.

Par défaut, cela se produit une fois que la promesse renvoyée par votre handler est résolue. Toutefois, si le défilement est plus pertinent à un stade antérieur, 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 du focus

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 refuser 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 présenter :

  • Si le Promise renvoyé est exécuté (ou si vous n'avez pas appelé intercept()), l'API Navigation déclenchera "navigatesuccess" avec un Event.
  • Si le Promise renvoyé est refusé, 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 suit:

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énement "navigateerror", qui reçoit un ErrorEvent, est particulièrement pratique, car il est garanti qu'il recevra toutes les erreurs de votre code qui configure une nouvelle page. Vous pouvez simplement await fetch(), sachant que si le réseau est indisponible, l'erreur sera finalement 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 permettant d'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 si l'URL ou l'état de l'entrée actuelle change. Il est toujours dans le même créneau. À l'inverse, si un utilisateur appuie sur "Retour", puis rouvre la même page, key change, car cette nouvelle entrée crée un nouvel emplacement.

Pour un développeur, key est utile, car l'API Navigation vous permet de rediriger directement l'utilisateur vers une entrée avec une clé correspondante. Vous pouvez le conserver, même dans les états d'autres entrées, pour 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 fait apparaître la notion d'"état". Il s'agit d'informations fournies par le développeur qui sont stockées de manière persistante sur l'entrée actuelle de l'historique, mais qui ne sont pas directement visibles par l'utilisateur. Cette méthode est extrêmement 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 n'importe quelle entrée) pour renvoyer une copie de son état:

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

Par défaut, il s'agit de undefined.

État du paramètre

Bien que les objets d'état puissent être modifiés, ces modifications ne sont pas enregistrées avec l'entrée de l'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

La méthode correcte pour définir l'état consiste à naviguer 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}), puis votre écouteur "navigate" peut appliquer cet état. Toutefois, il arrive que le changement d'état soit déjà entièrement appliqué au moment où votre code en est informé, par exemple lorsque l'utilisateur active ou désactive un élément <details> ou modifie l'état d'une entrée de formulaire. Dans ce cas, vous pouvez mettre à jour l'état afin que ces modifications soient conservées lors des rechargements et des traversées. Cela est possible en utilisant updateCurrentEntry():

navigation.updateCurrentEntry({state: newState});

Il existe également un événement pour être informé de ce changement:

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

Toutefois, si vous réagissez aux changements d'état dans "currententrychange", vous risquez de diviser ou même de dupliquer votre code de gestion des états entre l'événement "navigate" et l'événement "currententrychange", alors que navigation.reload({state: newState}) vous permet 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 l'ensemble de l'état de votre application. Toutefois, dans de nombreux cas, il est préférable de stocker cet état dans l'URL.

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

Accéder à toutes les entrées

L'entrée actuelle n'est cependant pas tout. L'API permet également d'accéder à la liste complète des entrées qu'un utilisateur a consultées lorsqu'il utilisait votre site via son appel navigation.entries(), qui renvoie un tableau instantané des entrées. Cela peut être utilisé, par exemple, pour afficher une interface utilisateur différente en fonction de la façon dont l'utilisateur a accédé à une page spécifique, ou simplement pour 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 individuelles, qui se déclenche lorsque l'entrée ne fait plus partie de l'historique du navigateur. Cela peut se produire lors 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 un long annexe dans les spécifications de tous les types possibles.)

Bien que pour de nombreux sites, le cas le plus courant soit celui où l'utilisateur clique sur un <a href="...">, il existe deux types de navigation plus complexes qui méritent d'être abordés.

Navigation programmatique

La première est la navigation programmatique, où la navigation est provoquée par un appel de méthode dans votre code côté client.

Vous pouvez appeler navigation.navigate('/another_page') n'importe où dans votre code pour déclencher une navigation. Cette opération sera gérée par l'écouteur d'événement centralisé enregistré sur l'écouteur "navigate", et votre écouteur centralisé sera appelé de manière synchrone.

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

La méthode navigation.navigate() renvoie un objet contenant deux instances Promise dans { committed, finished }. Cela permet au demandeur d'attendre que la transition soit "validée" (l'URL visible a été modifiée et qu'un nouveau NavigationHistoryEntry soit disponible) ou "terminée" (toutes les promesses renvoyées par intercept({ handler }) sont complètes, 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 qu'il est disponible via la méthode .getState() sur 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 pour, par exemple, indiquer une animation particulière qui fait apparaître la page suivante. Vous pouvez également définir une variable globale ou l'inclure dans le #hash. Les deux options sont un peu maladroites.) Notez que cette info ne sera pas rejouée si un utilisateur provoque ultérieurement une navigation, par exemple via les boutons "Précédent" et "Suivant". En fait, il s'agit toujours de undefined dans ces cas.

Démonstration de l'ouverture depuis la gauche ou la droite

navigation comporte également plusieurs autres méthodes de navigation, qui renvoient toutes un objet contenant { committed, finished }. J'ai déjà mentionné traverseTo() (qui accepte un key qui désigne une entrée spécifique dans 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 centralisé "navigate".

Envois de formulaires

Deuxièmement, l'envoi d'<form> HTML via POST est un type de navigation particulier, et l'API Navigation peut l'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 sur NavigateEvent. Voici un exemple qui transforme simplement l'envoi d'un formulaire en envoi 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énement "navigate", la spécification actuelle de l'API Navigation ne déclenche pas "navigate" lors du premier chargement d'une page. Pour les sites qui utilisent l'affichage côté serveur (SSR) pour tous les états, cela peut être acceptable. Votre serveur peut renvoyer l'état initial correct, ce qui est le moyen le plus rapide de diffuser du contenu auprès de vos utilisateurs. Toutefois, les sites qui utilisent le code côté client pour créer leurs pages peuvent avoir besoin de créer une fonction supplémentaire pour initialiser leur page.

Un autre choix intentionnel de conception de l'API Navigation est qu'elle ne fonctionne que dans un seul frame, c'est-à-dire la page de premier niveau ou un seul <iframe> spécifique. Cela a un certain nombre d'implications intéressantes qui sont décrites plus en détail dans la spécification, mais dans la pratique, cela réduit la confusion des développeurs. L'ancienne API History comporte un certain nombre de cas particuliers déroutants, comme la prise en charge des cadres. L'API Navigation repensée gère ces cas particuliers dès le départ.

Enfin, il n'existe pas encore de consensus sur la manière de modifier ou de réorganiser de façon programmatique la liste des entrées que l'utilisateur a parcourue. Cette question est actuellement en cours d'examen, mais une option pourrait être de n'autoriser que les suppressions : soit des entrées historiques, soit "toutes les entrées futures". Ce dernier permettrait un état temporaire. Par exemple, en tant que développeur, je peux:

  • 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 de l'historique à la fin d'une tâche ;

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

Essayer l'API Navigation

L'API Navigation est disponible dans Chrome 102 sans flag. Vous pouvez également essayer une démonstration 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 à des cas particuliers et à la façon dont elle a été implémentée différemment dans les différents navigateurs. Nous espérons que vous envisagerez de nous faire part de vos commentaires sur la nouvelle API Navigation.

Références

Remerciements

Merci à Thomas Steiner, Domenic Denicola et Nate Chapin d'avoir relu cet article.