WICG / navigation-api

The new navigation API provides a new interface for navigations and session history, with a focus on single-page application navigations.
https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api
486 stars 26 forks source link

Deferred navigation commits // `Navigation.lastSuccessfulEntry` #277

Open bramus opened 14 hours ago

bramus commented 14 hours ago

Problem Statement

Demo page: https://view-transitions.netlify.app/stack-navigator/mpa-with-navigation-api/

Consider this scenario: you are on index.html and click a link to detail.html which gets intercepted to load the necessary data via fetch. While the data of detail.html is still being fetched (due to a slow network, for example) you click the home icon which links to index.html (again via interception).

The Navigation API reports three entries:

  1. index.html
  2. detail.html (interrupted)
  3. index.html

While this list is Technically Correct™, UX-wise the visitor never saw the detail page, so they perceive it as going from index to index.

This problem becomes visible when throwing View Transitions in the mix, in which I use the current and target URL to determine which type of transition to run.

Something like this:

navigation.addEventListener("navigate", (e) => {
    if (shouldNotIntercept(e)) {
        return;
    }

    const currentURL = new URL(navigation.currentEntry.url); // 👈
    const destinationURL = new URL(e.destination.url);

    e.intercept({
        handler: async () => {

            // …

            const transitionClass = determineTransitionClass(
                currentURL,
                destinationURL
            );
            document.documentElement.dataset.transition = transitionClass;

            await document.startViewTransition(…);

            // …

        }
    });
});

In the scenario I described earlier, where the navigation to detail.html gets interrupted, determining the value for the currentURL using navigation.currentEntry is unreliable. Upon clicking the home icon in the scenario, the reported currentURL is detail.html, even though that navigation was never finished. As a result, the View Transition that will eventually run is a slide-backwards animation with the index.html page, as the code has determined it is going from detail.html to index.html.

Visually this is incorrect: the new index.html page should not slide in in this case, as the user was still seeing the old index.html page.

Proposed Solution(s)

Give authors control to defer a commit

Part of the original explainer included a section on how to defer a commit, but one had to commit it manually. This has not made it into the final specification, for reasons unknown to me.

Building upon that idea, it would look something like this, which would only commit an entry after a successful navigation.

e.intercept({
  handler,
  commit: "after-navigation-success" // 👈
});

Keep track of of Navigation.lastSuccessfulEntry

Expose a lastSuccessfulEntry property on the Navigation object. The Navigation API updates this value automatically after a successful navigation has finished.

navigation.addEventListener("navigate", (e) => {
    if (shouldNotIntercept(e)) {
        return;
    }

    e.intercept({
        handler: async () => {

            // …

            const transitionClass = determineTransitionClass(
                new URL(navigation.lastSuccessfulEntry.url), // 👈
                new URL(navigation.currentEntry.url)
            );
            document.documentElement.dataset.transition = transitionClass;

            await document.startViewTransition(…);

            // …
        }
    });
});

The advantage to using Navigation.lastSuccessfulEntry is that it is something that can easily be polfyilled:

// Get initial value on page load
navigation.lastSuccessfulEntry = navigation.currentEntry;

// Update after a successful navigation
navigation.addEventListener('navigatesuccess', e => {
    navigation.lastSuccessfulEntry = e.currentTarget.currentEntry;
});

I have used this approach in https://view-transitions.netlify.app/stack-navigator/mpa-with-navigation-api/

Because it is that easy to polyfill, you could argue that in that case it’s not necessary to build this into the Navigation API, though.

posva commented 12 hours ago

I’m also interested in the commit the url change after navigation. I thought it was agreed it was crucial for routers and that such feature would be added