• Stars
    star
    335
  • Rank 125,904 (Top 3 %)
  • Language
  • Created about 8 years ago
  • Updated about 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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.

More Repositories

1

svgomg

Web GUI for SVGO
JavaScript
5,792
star
2

idb

IndexedDB, but with promises
TypeScript
5,582
star
3

idb-keyval

A super-simple-small promise-based keyval store implemented with IndexedDB
TypeScript
2,446
star
4

sprite-cow

Sprite Cow helps you get the background-position, width and height of sprites within a spritesheet as a nice bit of copyable css.
JavaScript
1,280
star
5

offline-wikipedia

Demo of how something like Wikipedia could be offline-first
HTML
812
star
6

isserviceworkerready

Tracking the status of ServiceWorker in browsers
HTML
563
star
7

simple-serviceworker-tutorial

A really simple ServiceWorker example, designed to be an interactive introduction to ServiceWorker
JavaScript
390
star
8

wittr

Silly demo app for an online course
JavaScript
387
star
9

trained-to-thrill

Trains! Yey!
JavaScript
325
star
10

appcache-demo

Python
242
star
11

jakearchibald.com

TypeScript
226
star
12

sass-ie

Writing mobile-first styles without leaving IE<9 behind
Shell
185
star
13

big-web-quiz

JavaScript
110
star
14

linear-easing-generator

TypeScript
102
star
15

wordle-analyzer

TypeScript
95
star
16

tweetdeck-prototype

(mobile|offline)-first Tweetdeck prototype
JavaScript
79
star
17

request-quest

JavaScript
66
star
18

git-convenience

Tools to make git on the terminal a little more pleasureable
Shell
61
star
19

I-rudely-reject-pull-requests-to-this-repo

I will rudely and childishly reject pull requests to the repo
57
star
20

typescript-worker-example

TypeScript
55
star
21

streaming-html

HTML
52
star
22

http2-push-test

JavaScript
51
star
23

safari-14-idb-fix

Working around a Safari IndexedDB bug
JavaScript
42
star
24

cors-playground

TypeScript
41
star
25

sw-routes

Just playing with some ideas
TypeScript
41
star
26

byte-storage

41
star
27

http203-playlist

JavaScript
33
star
28

preso

JavaScript
32
star
29

async-waituntil-polyfill

JavaScript
30
star
30

streaming-include

Throwing around design ideas for a streaming include api
JavaScript
29
star
31

Woosh

Speed testing framework
JavaScript
28
star
32

houdini-paint-flecks

JavaScript
22
star
33

responsive-gallery

An experiment in client-side responsive imagery
JavaScript
18
star
34

LinkTracker

A simple bit of JavaScript to catch clicks to a link that can be logged by any server-side tracking system
JavaScript
18
star
35

sse-fetcher

Server-sent events rewritten on top of fetch
TypeScript
18
star
36

wittr-modern

CSS
17
star
37

easing-worklet

15
star
38

canvas-snow

JavaScript
15
star
39

jank-invaders

JavaScript
15
star
40

mankini

JavaScript
15
star
41

google-album-downloader

Just playing around with some ideas
TypeScript
13
star
42

minesweeper

Just doodling
TypeScript
13
star
43

payments

JavaScript
12
star
44

me

All about me.
12
star
45

ebook-demo

JavaScript
10
star
46

remove-old-service-worker

JavaScript
10
star
47

sw-cache-update-example

JavaScript
10
star
48

scrolly-cliche

JavaScript
10
star
49

font-testing

Testing @font-face support in browsers
PHP
10
star
50

f1-site-optim

Just playing around
TypeScript
9
star
51

supercharged-blog

CSS
9
star
52

range-request-test

This tests how browsers cope with video & range requests.
JavaScript
9
star
53

sass-lessons

Small sass project for a workshop
Shell
9
star
54

frontend-lessons

Teaching someone bits of frontend
8
star
55

flickr-set-fetcher

One-way syncs a Flickr set to disk
JavaScript
7
star
56

streaming-html-spec

JavaScript
7
star
57

image-experiments

Just playing
TypeScript
7
star
58

send-more-bytes-than-length

JavaScript
6
star
59

wikipedia-and-dictionary-title-search

TypeScript
6
star
60

rollup-import-maps-demo

JavaScript
6
star
61

service-worker-benchmark

HTML
6
star
62

configs

Shell
6
star
63

google-photos-downloader-deno

TypeScript
6
star
64

simple-transition

A small library for controlling transitions with JavaScript
JavaScript
5
star
65

sketch-chain

TypeScript
5
star
66

appcache2serviceworker

Not ready yet
JavaScript
5
star
67

ZoomFix-jQuery-Plugin

Patching jQuery functions that return incorrect values when various browsers are zoomed
JavaScript
5
star
68

webwords

JavaScript
5
star
69

http-tinkering

JavaScript
5
star
70

portal-demos

HTML
5
star
71

img-source-sizes-test

TypeScript
4
star
72

jakearchibalddemos

HTML
4
star
73

project-start

Project bootstrapping
JavaScript
4
star
74

f1predictions

F1 predictions thingy. Just me playing with Django really
Python
4
star
75

module-script-demos

JavaScript
3
star
76

accept-encoding-range-test

JavaScript
3
star
77

showrss-downloader

JavaScript
3
star
78

mq-apply

JavaScript
3
star
79

http203-slides

JavaScript
3
star
80

h2-priority-test

JavaScript
3
star
81

streaming-handlebars

An experiment
JavaScript
3
star
82

appcache-credentials

Demo of appcache security issue
JavaScript
3
star
83

money-manager

JavaScript
3
star
84

cache-credentials

JavaScript
2
star
85

idb-keyval-build-example

JavaScript
2
star
86

rollup-hash-bug

JavaScript
2
star
87

thing.html

HTML
2
star
88

svgomg-slow

HTML
2
star
89

idb-minimal-webpack

A minimal webpack config showing the IDB library
JavaScript
2
star
90

rollup-hash-bug-2

JavaScript
2
star
91

pinch-zoomer

JavaScript
2
star
92

chunked-encoding-request-test

JavaScript
1
star
93

jakearchibald

1
star
94

thing.txt

HTML
1
star
95

xbmc-remote

JavaScript
1
star
96

multi-dev

Test project for a workshop
1
star
97

rollup-child-build

Just playin
JavaScript
1
star
98

multi-thread-svg-render

1
star
99

static-stuff

GLSL
1
star
100

history-timeline-generator

TypeScript
1
star