Routage côté client moderne: API Navigation

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

Navigateurs pris en charge

  • 102
  • 102
  • x
  • x

Source

Les applications monopages, ou SPA, sont définies par une fonctionnalité essentielle: la réécriture dynamique du contenu lorsque l'utilisateur interagit avec le site, au lieu de la méthode par défaut consistant à charger de toutes nouvelles pages à partir du serveur.

Même si les applications Web monopages ont pu vous faire bénéficier de 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 intuitive développée bien avant que les applications Web soient la norme, et le Web réclame une toute nouvelle approche. L'API Navigation est une proposition d'API qui remanie entièrement cet espace, au lieu d'essayer de corriger les défauts 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. Pour 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 global navigation. Fondamentalement, cet événement est centralisé: il se déclenche pour tous les types de navigations, que l'utilisateur ait effectué une action (par exemple, cliquer sur un lien, envoyer un formulaire ou passer à la page suivante), 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 garder l'utilisateur sur la même longueur d'onde et de charger ou modifier le contenu du site.

Un NavigateEvent est transmis à l'écouteur "navigate". Celui-ci contient des informations sur la navigation, telles que l'URL de destination, et vous permet de répondre à la navigation depuis 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:

  • Appeler intercept({ handler }) (comme décrit ci-dessus) pour gérer la navigation.
  • Appeler preventDefault(), 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 entraîne la création d'un objet de transition, navigation.transition, qu'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, il est impossible de les appeler. 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 être en mesure de piéger les utilisateurs sur votre site. (Ce sujet 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 sur la plate-forme ?

Un écouteur d'événements "navigate" centralise la gestion des modifications d'URL dans une SPA. Cette proposition est délicate à réaliser 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 parfait, mais cette liste n'est pas exhaustive. Les liens peuvent apparaître et disparaître sur votre page, et il ne s'agit pas du seul moyen pour les utilisateurs de naviguer dans les pages. Par exemple, ils peuvent envoyer un formulaire ou même utiliser une image cliquable. Votre page peut gérer ces problèmes, mais de nombreuses possibilités peuvent être simplifiées, ce que permet la nouvelle API Navigation.

De plus, la commande ci-dessus ne gère pas la navigation vers l'avant/l'arrière. Il y a un autre événement correspondant, "popstate".

Personnellement, l'API History pense souvent qu'elle pourrait contribuer à ces possibilités. Cependant, il n'a en réalité que deux surfaces: répondre si l'utilisateur appuie sur "Retour" ou "Suivant" dans le navigateur, et pousser et remplacer des URL. Il n'a pas d'analogie avec "navigate", sauf si vous configurez manuellement des écouteurs pour les événements de clic, par exemple, comme illustré 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.

Les propriétés clés sont les suivantes:

canIntercept
Si la valeur est "false", vous ne pouvez pas intercepter la navigation. Les navigations multi-origines et les balayages entre documents ne peuvent pas être interceptés.
destination.url
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 être utilisé pour créer des liens vers différentes parties du document actuel. Ainsi, si hashChange est défini sur "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 cet élément.
formData
Si la valeur n'est pas nulle, cela signifie que cette navigation fait partie de l'envoi d'un 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 sur la gestion des envois de formulaires plus loin dans cet article.
navigationType
Valeurs possibles : "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 se présenter comme suit:

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 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 (qui se produit automatiquement avec les async functions), celle-ci indique au navigateur la durée de la navigation et sa réussite.

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

Ainsi, cette API introduit un concept sémantique que le navigateur comprend: une navigation SPA est en cours, au fil du temps, en remplaçant le document par une URL et un état précédents. Cela présente un certain nombre d'avantages potentiels, y compris l'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. (Cette situation ne se produit pas actuellement lorsque l'utilisateur accède aux boutons "Précédent" et "Suivant", mais ce problème sera bientôt résolu.

Lorsque vous interceptez des navigations, la nouvelle URL prend effet juste avant que votre rappel handler ne soit appelé. 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 permet non seulement d'éviter les problèmes de résolution des URL, mais aussi d'être plus 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(), la navigation peut devenir 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 gérer l'une de ces possibilités, l'événement transmis à l'écouteur "navigate" contient une propriété signal, qui est de type AbortSignal. Pour en savoir plus, consultez la section Récupération annulée.

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 effectuez à fetch(), ce qui annule les requêtes réseau en cours de transfert 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 le 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, avec getArticleContent intégré, qui montre 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 utilisez intercept() une navigation, le navigateur tente de gérer automatiquement le défilement.

Pour les navigations vers une nouvelle entrée d'historique (lorsque navigationEvent.navigationType est "push" ou "replace"), cela signifie essayer de faire défiler jusqu'à la partie indiquée par le fragment d'URL (l'élément après le #) ou réinitialiser le défilement vers le 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 la promesse renvoyée par votre handler résolue, mais s'il est judicieux de faire défiler la page précédemment, 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 ciblage

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 la Promise renvoyée est exécutée (ou si vous n'avez pas appelé intercept()), l'API Navigation déclenchera "navigatesuccess" avec un Event.
  • Si l'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énements "navigateerror", qui reçoit un ErrorEvent, est particulièrement utile, car il vous garantit de recevoir toutes les erreurs de votre code qui configure une nouvelle page. Vous pouvez simplement await fetch() 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 la position actuelle 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 pour 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 se trouve toujours au même emplacement. À l'inverse, si un utilisateur appuie sur "Retour", puis rouvre la même page, key change, car cette nouvelle entrée crée un emplacement.

Pour les développeurs, 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 à l'état 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 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 est améliorée par rapport à celle-ci.

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

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

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

Définition de l'état

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

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 correspondre à 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. Cependant, il arrive parfois 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/désactive un élément <details> ou modifie l'état d'une entrée de formulaire. Dans ce cas, il peut être utile de mettre à jour l'état afin que ces modifications soient conservées par le biais d'actualisations et de balayages. 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 à des changements d'état dans "currententrychange", vous risquez de diviser ou même de dupliquer votre code de gestion d'état entre l'événement "navigate" et l'événement "currententrychange", tandis que navigation.reload({state: newState}) vous permettrait de le gérer à un seul et même endroit.

Différence entre l'état et les paramètres d'URL

Un état pouvant être un objet structuré, vous pourriez être tenté 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 pensez que l'état est 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

Toutefois, l'entrée actuelle ne fait pas tout. L'API permet également d'accéder à la liste complète des entrées qu'un utilisateur a parcourue sur 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 également 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 mentionné ci-dessus. (Il y a en fait une longue annexe dans la spécification pour tous les types possibles.)

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

Navigation programmatique

Le premier est la navigation programmatique, où la navigation est causé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énements centralisé enregistré dans l'écouteur "navigate", qui sera appelé de manière synchrone.

Il s'agit d'une agrégation améliorée des anciennes méthodes comme location.assign() et des amis, ainsi que des 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 é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 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 actuelle de l'historique.
  • info: objet à transmettre à l'événement de navigation via navigateEvent.info.

info peut notamment être utile pour indiquer, par exemple, une animation spécifique qui déclenche l'affichage de la page suivante. (L'alternative peut être de définir une variable globale ou de l'inclure dans le #hash. Les deux options sont un peu gênantes.) Il est à noter que cet élément info ne sera pas relancé si un utilisateur déclenche ultérieurement la navigation, par exemple via ses boutons "Précédent" et "Suivant". Dans ce cas, la valeur est toujours undefined.

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

navigation comporte également plusieurs autres méthodes de navigation, qui renvoient toutes un objet contenant { committed, finished }. Nous avons déjà mentionné traverseTo() (qui accepte un key qui indique 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

Ensuite, l'envoi HTML <form> 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 sur NavigateEvent. Voici un exemple qui transforme simplement n'importe quel formulaire envoyé 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 (SSR, Server Side Rendering) pour tous les états. En effet, votre serveur peut renvoyer l'état initial correct, ce qui constitue le moyen le plus rapide de transmettre du contenu à 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'API History précédente présente un certain nombre de cas limites déroutants, tels que la prise en charge des frames. L'API Navigation repensée les gère 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 option est en cours de discussion, mais vous pouvez autoriser uniquement les suppressions: soit des entrées historiques, soit "toutes les entrées futures". Le second autoriserait l'é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 d'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). Cela n'est tout simplement pas possible avec l'API History actuelle.

Essayer l'API Navigation

L'API Navigation est disponible dans Chrome 102 sans options. 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 concernant les cas particuliers et la façon dont elle a été implémentée différemment selon les navigateurs. Nous espérons que vous souhaiterez 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. Image héros de la série Unsplash, de Jeremy Zero.