sveltejs / sapper

The next small thing in web development, powered by Svelte
https://sapper.svelte.dev
MIT License
7k stars 434 forks source link

Handling different kinds of page transitions #295

Open Rich-Harris opened 6 years ago

Rich-Harris commented 6 years ago

Bear with me while I try and get my thoughts in order — this isn't a particular bug or a concrete proposal, just a few things I'm thinking about around the ways in which we need to improve or flesh out Sapper's behaviour around page transitions.

A typical App.html looks like this:

<Nav path={props.path}/>

<main>
  <svelte:component this={Page} {...props}/>
</main>

<style>
  main {
    position: relative;
    background-color: white;
    padding: 2em;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<script>
  import Nav from '../components/Nav.html';

  export default {
    components: {
      Nav
    }
  };
</script>

Navigating to a new page

When you navigate from (say) / to /about, the value of Page changes from the component defined by routes/index.html to the component defined by routes/about/index.html. At the same time, props becomes a new object.

This works well; the index page is destroyed, and the about page is recreated. Any transitions on the two pages are disregarded, however.

Navigating to the 'same' page

A trickier case is when you navigate from e.g. /posts/first-post to /posts/second-post, where the component in question is routes/posts/[slug].html. In this case, the value of Page is unchanged. Consequently, while the page's preload function is invoked, any component data that is not determined by preload is retained, and lifecycle hooks are ignored.

We could solve this like so...

app.set({ Page: null });
app.set({ Page });

...which would cause the page to be destroyed and recreated. But we'd need to make sure that intros and outros still worked in that context.

Another consideration: maybe we don't want the page to be destroyed in that context. Maybe the page is routes/settings/[submenu].html and we're navigating from /settings to /settings/notifications, and the appropriate thing to do is to keep the settings page intact while showing the submenu. (Perhaps there's an <input> with some data that we don't want to lose, or something.)

So we have a few 'same page' transition categories to support:

The distinction between the two cases (destroy vs persist) might even vary within the app. So it's possible that it can't be handled with configuration.

I would argue that destroy/recreate should be the default behaviour.

Transition modes

When the page you're navigating from and the page you're navigating to both have transitions, you might sometimes want the outro to happen before the intro. Other times you might want them to happen simultaneously.

I'm not sure what the best way to express that would be (again, it might differ within an app).

Thoughts

// app/client.js
init({
  target: document.querySelector('#sapper'),
  routes,
  App,
  persist: ({ oldPath, newPath }) => {
    // this is invoked when the user navigates
    // but Page is unchanged
    return newPath.startsWith('settings');
  }
});
Rich-Harris commented 6 years ago

I believe most of the questions here are answered by #262 (notwithstanding the implementation challenges) — the out-then-in or out-and-in-simultaneously question is the tricky remaining one.