Giter VIP home page Giter VIP logo

navigation-transitions's Introduction

The problem

If you want to transition between pages, your current option is to fetch the new page with JavaScript, update the URL with pushState, and animate between the two.

Having to reimplement navigation for a simple transition is a bit much, often leading developers to use large frameworks where they could otherwise be avoided. This proposal provides a low-level way to create transitions while maintaining regular browser navigation.

Goals

  • Enable complex transitions.
  • Allow transitions to start while the next page is being fetched.
  • Allow transitions to differ between navigation from clicking a link, back button, forward button, reload button, etc.
  • Allow transitions to cater for a non-zero scroll position in the navigated-to page.

Experiments, talks & reading materials

Web Navigation Transitions (2014): CSS-based experiment by Google Chrome team

Web Navigation Transitions (2015): CSS-based proposal by Chris Lord (Mozilla, Firefox OS)

Web Navigation Transitions (2016): New proposal by Jake Archibald (Google Chrome)

API sketch

window.addEventListener('navigate', event => {
  // …
});

The navigate event fires when the document is being navigated in a way that would replace the current document.

  • event.type - The name of this event, navigate.
  • event.reason - The way in which the document is being navigated to. One of the following strings:
    • back - User is navigating back.
    • forward - User is navigating forward.
    • reload - Reload-triggered navigation.
    • normal - Not one of the above.
  • event.url - The URL being navigated to. An empty string if the URL is of another origin.
  • event.newWindow - A promise for a WindowProxy being navigated to. Resolves with undefined if another origin is involved in the navigation (i.e., the initial URL or URLs of redirects). Rejects if the navigation fails. Cancels if the navigation cancels (dependent on cancelable promises).
  • event.transitionUntil(promise) - Keep this document alive and potentially visible until promise settles, or once another origin is involved in the navigation (i.e., the initial URL or URLs of redirects).

Note: The same-origin restrictions are to avoid new URL leaks and timing attacks.

Simple cross-fade transition

window.addEventListener('navigate', event => {
  event.transitionUntil(
    event.newWindow.then(newWin => {
      if (!newWin) return;

      // assuming newWin.document.interactive means DOM ready
      return newWin.document.interactive.then(() => {
        return newWin.document.documentElement.animate([
          {opacity: 0}, {opacity: 1}
        ], 1000).finished;
      });
    })
  );
});

Slide-in/out transition

window.addEventListener('navigate', event => {
  if (event.reason == 'reload') return;

  const fromRight = [
    {transform: 'translate(100%, 0)'},
    {transform: 'none'}
  ];

  const toLeft = [
    {transform: 'none'},
    {transform: 'translate(-100%, 0)'}
  ];

  const fromLeft = toLeft.slice().reverse();
  const toRight = fromRight.slice().reverse();

  event.transitionUntil(
    event.newWindow.then(newWin => {
      if (!newWin) return;
 
      return newWin.document.interactive.then(() => {
        return Promise.all([
          newWin.document.documentElement.animate(
            event.reason == 'back' ? fromLeft : fromRight, 500
          ).finished,
          document.documentElement.animate(
            event.reason == 'back' ? toRight : toLeft, 500
          ).finished
        ]);
      });
    })
  );
});

Immediate slide-in/out transition

The above examples don't begin to animate until the new page has fetched and become interactive. That's ok, but this API allows the current page to transition while the new page is being fetched, improving the perception of performance:

window.addEventListener('navigate', event => {
  if (event.reason == 'reload') return;

  const newURL = new URL(event.url);

  if (newURL.origin !== location.origin) return;

  const documentRect = document.documentElement.getBoundingClientRect();

  // Create something that looks like the shell of the new page
  const pageShell = createPageShellFor(event.url);
  document.body.appendChild(pageShell);

  const directionMultiplier = event.reason == 'back' ? -1 : 1;

  pageShell.style.transform = `translate(${100 * directionMultiplier}%, ${-documentRect.top}px)`;

  const slideAnim = document.body.animate({
    transform: `translate(${100 * directionMultiplier}%, 0)`
  }, 500);

  event.transitionUntil(
    event.newWindow.then(newWin => {
      if (!newWin) return;
 
      return slideAnim.finished.then(() => {
        return newWin.document.documentElement
          .animate({opacity: 0}, 200).finished;
      });
    })
  );
});

Rendering & interactivity

During the transition, the document with the highest z-index on the documentElement will render on top. If z-indexes are equal, the entering document will render on top. Both documentElements will generate stacking contexts.

If the background of html/body is transparent, the underlying document will be visible through it. Beneath both documents is the browser's default background (usually white).

During the transition, the render-box of the documents will be clipped to that of the viewport size. This means html { transform: translate(0, -20px); } on the top document will leave a 20-pixel gap at the bottom, through which the bottom document will be visible. After the transition, rendering switches back to using the regular model.

We must guarantee that the new document doesn't visibly appear until event.newWindow's reactions have completed.

As for interactivity, both documents will be at least scrollable, although developers could prevent this using pointer-events: none or similar.

Apologies for the hand-waving.

Place within the navigation algorithm

It feels like the event should fire immediately after step 10 of navigate. If transitionUntil is called, the browser would consider the pages to be transitioning.

The rest of the handling would likely be in the "update the session history with the new page" algorithm. The unloading of the current document would be delayed but without delaying the loading of the new document.

Yep, more hand-waving.

Potential issues & questions

  • Can transitions/animations be reliably synchronised between documents? They at least share an event loop.
  • Any issues with firing transitions/animations for nested contexts?
  • What if the promise passed to transitionUntil never resolves? Feels like it should have a timeout.
  • What happens on low-end devices that can't display two documents at once?
  • What if the navigation is cancelled (e.g., use of a Content-Disposition response header). event.newWindow could also cancel.
  • How does this interact with browsers that have a back-forward cache?
  • How should redirects be handled?
  • How should interactivity during the transition be handled?
  • During a sliding transitions, is it possible to switch a fake shell for the actual page's document mid-transition? Feels like this is something the Animation API should be able to do.

navigation-transitions's People

Contributors

cvan avatar jakearchibald avatar odino avatar simevidas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

navigation-transitions's Issues

Define navigation in Workers

Based on the proposed API in the README, you'd do this:

window.addEventListener('navigate', event => {
   // …
});

Would it be possible to move this to a worker to intercept or listen to navigation events, something like this?

self.addEventListener('fetch', function (event) {
  event.respondWithTransitionUntil();
});

Thoughts?

Client side rendered apps?

often leading developers to use large frameworks where they aren't needed

Reading through the README, I feel like this API is primarily targeting server-side rendered apps.
I have been using a transitions library to solve this very same issue.
I'm not sure what the big picture is.
Do you confirm that this new API wouldn't help for client side rendered apps?

Scroll position example

You mention this API is interesting for managing scroll position across links:

Allow transitions to cater for a non-zero scroll position in the navigated-to page.

It would be nice to see an example here. (I'm guessing you'd be operating on document.scrollingElement.scrollTo.)

Status of this proposal

This comment from a WICG forum post states that this proposal has received pushback Googlers. The objection stated is:

My proposal got push-back internally because it requires keeping both documents in memory at once for a bit.

Since I can't quite understand why this is a problem would you mind answering a few questions?

  1. Can you expand on the objection if there is more information?
  2. Do the objectors feel strongly that this is a "deal breaker" for this proposal?
  3. If so, is there any work-around to the problem that you have thought of yet?

Define (possibly as non-normative text) how to handle WebGL/canvas transitions

AFAIK, there is no way to tell whether a canvas is being drawn to (without monkey-patching the prototype of the interfaces). But it would be nice to be able to have a WebGL scene of a document transition to another scene's (primary) WebGL scene upon navigation. This could be done with the current proposed psuedocode, but I think some platform changes or special-casing may be required. Thoughts?

Guarantees about cheap layout data?

Looking at the example in the explainer, I was wondering if the spec should make any guarantees about how cheap calls to getBoundingClientRect() and similar are.

Transition/Animation code is going to need that kind of data by very nature, so it’d be good if calls like this would not force layout (unless style changes). Maybe even attach this kind of data to the event?

Potential for memory leaks?

After the result of transitionUntil resolves, is there any garbage collection that occurs of the outgoing page? Unless the spec explicitly nulls out all references to the outgoing document (and its descendants), I wonder if it's too easy to accidentally close-over some-or-all of the old document, preventing it from being GCed.

Would this also solve updating only part of a page?

Let's say you've got a header and some other static parts on a page. Nowadays you'd fetch the new content with fetch and replace the relevant dom with the new content. Would it make sense to use 'navigate' and somehow only get some of the content you'd then embed on a certain place on the page, but still be able to go to that page regularly and get the whole document.

Should the UA/user be able to disable Navigation Transitions?

I imagine in that event that some actors will implement some less-than-ideal transitions, users will expect the User Agent to intervene (or allow intervention) to disable Transitions. This sounds to me like non-normative text in the spec.

A few possible solutions or considerations that come to mind:

  • There could be a browser-level flag to disable Transitions.
  • A transitions permission type could be added to the Permissions API. By default, calling Permissions.query('transitions') could resolve {granted: true, permission: Permission {name: "transitions"}>}. No permission prompt would appear by default for documents, but the user would have the ability to disable Transitions on a case-by-case basis for a site/domain.
  • There could be an algorithm for choosing whether a browsing context can trigger Transitions (à la the algorithm for browsing contexts that handles popups that obey the UA's popup blockers).
  • In the case of causing user danger (not just annoyance), the Safe Browsing API (which is used by the browsers to prevent loading phishy, malware, dangerous sites) could be used to ban nefarious actors of Navigation Transitions. (I've been thinking about this for a while, so I'll file a separate issue about the possible attack vectors and mitigation.)

Keeping headerbar/sidemenu in place

I was wondering if and how this API could implement a static (and even functional!) headerbar/sidemenu throughout transitions like shown in this video (450KB). I’d like this API to be able to do this, but not sure if there’s any implications I am missing.

Taking some inspiration from the “Immediate Slide in/out transition” example, this is what I came up with:

window.addEventListener('navigate', event => {
  if (event.type == 'reload') return;

  const newURL = new URL(event.url);
  if (newURL.origin != location.origin) return;

  const oldContent = document.querySelector('#content');
  // Does some kind of animation so that the content div is not visible anymore
  const outAnimation = animateOut(oldContent);

  event.transitionUntil(async function() {
    await outAnimation;
    const newWin = await event.newWindow;
    const newContent = newWin.document.documentElement.querySelector('#content');
    // Styles that move content out-of-view so it can be animated in
    newContent.classList.add('about-to-be-animated-in');
    oldContent.parentNode.replaceChild(newContent, oldContent);
    return animateIn(newContent);
  }());
});

Would this work? Should this work?

@jakearchibald @paullewis WDYT?

Should `navigate` fire if the document "navigates" but loses visibility to the user?

As a developer, I may want to ensure that a transition begins only when either/or the originating document or destination document is visible to the user. Or, I may want to ensure the opposite: that a transition does occur. Using the Page Visibility API, I could check the visibility state of my document (document.visibilityState, document.hidden), but are there guarantees or assumptions I can make here about navigate firing?

Updates?

Hola! Is there any update on this proposal? As in, is it making progress within the Chrome team? :)

Would love to see something of this sort become a reality!

Stacking contexts

You mention that whichever document has the highest z-index ends up in front. Does this mean each document is its own stacking context?

Put another way, can the elements in the documents be interleaved, or are they effectively siloed into their respective document's z spaces?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.