Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ionic Page transition - how to implement in router? #32

Closed
Tommertom opened this issue Jul 10, 2022 · 21 comments
Closed

Ionic Page transition - how to implement in router? #32

Tommertom opened this issue Jul 10, 2022 · 21 comments
Labels
help wanted Extra attention is needed

Comments

@Tommertom
Copy link
Owner

Tommertom commented Jul 10, 2022

For page transitions Ionic has this really cool transition which is more than just a single page fly-in - what is happening now.

It looks into the various elements of the incoming component and gives them specific transitions. Like the header-title, the default back-button and the page itself. Also the outoging component gets this special treatment as a whole and for its elements.

For now I have been able to pin down this transition in their code. And here an example link to the transition in ios: https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/transition/ios.transition.ts

The function responsible for adding the transitions to the dom elements seems to be :


   const transition = (
      enteringEl: HTMLElement,
      leavingEl: HTMLElement,
      direction: any, // TODO types
      showGoBack: boolean,
      progressAnimation: boolean,
      animationBuilder?: AnimationBuilder
    ) => {

(taken from the vue implementation -and I need to check how it relates to the ios.transitions.ts)

The API requires the enteringElement and the leaving element, besides a directions. The showGoBack is related to the BackButton mostly shown top left or top right corner. animationBuilder is I believe a customer animation and the boolean I don't know, but probably not of big interest for now?

So, the question is how to hook this up in Routify. Maybe through the helpers you have? But I also think/fear/believe a more deeper connection might be needed.

Looking for help on how to achieve this.

@Tommertom Tommertom added the help wanted Extra attention is needed label Jul 10, 2022
@kristianmandrup
Copy link

I'd love to help. Looked into the routify code a bit.

Route.js

    async loadRoute() {
        const { router } = this
        const pipeline = [
            this.runBeforeUrlChangeHooks,
            this.loadComponents,
            this.runGuards,
            this.runPreloads,
        ]

    async loadComponents() {
        this.log.debug('load components', this) // ROUTIFY-DEV-ONLY
        await Promise.all(this.fragments.map(fragment => fragment.node.loadModule()))
        return true
    }


   // hook available from outsude
    async runBeforeUrlChangeHooks() {
        return await this.router.beforeUrlChange.run({ route: this })
    }

So it looks like the RouteFragments are each linked to a node (ie. a "component")

RouteFragment.js

export class RouteFragment {
    /**
     * @param {Route} route the route this fragment belongs to
     * @param {RNodeRuntime} node the node that corresponds to the fragment
     * @param {String} urlFragment a fragment of the url (fragments = url.split('/'))
     * @param {Object<string, any>} params
     */
    constructor(route, node, urlFragment, params) {
        this.route = route
        this.node = node
        /** @type {Partial<RoutifyLoadReturn>} */
        this.load = undefined
        this.urlFragment = urlFragment
        this.params = params
  }

The node is a RNodeRuntime which has a children and a component prop as well.

For route transitions

Apparently you can create your own decorators with transitions

In particular see the BaseTransition a Template for creating your own decorations.

  import { fade } from 'svelte/transition'

  const configs = [
    {
        condition: (meta)=>true,
        transition: fade,
        /** inParams: {},  optional **/
        /** outParams: {}  optional **/
    }
  ]

To customize transitions, see:

I think to fully customize navigation transitions, you would need to create your own variant of BaseTransition where you create your own variant of

<div
  class="transition node{get(node).__file.id}"
  in:transition|local={inParams}
  out:transition|local={outParams}
>
  <slot />
</div>

So you are not limited by the built-in in and out transitions?

@kristianmandrup
Copy link

This might be a good starting point: https://css-tricks.com/native-like-animations-for-page-transitions-on-the-web/

.page-enter-active {
  transition: opacity 0.25s ease-out;
}

.page-leave-active {
  transition: opacity 0.25s ease-in;
}

.page-enter,
.page-leave-active {
  opacity: 0;
}

I would think that the trick is to create and expose some kind of shared state (route store) between pages, then have particular page components activate their transitions on state changed? Then hook up the before and after helpers to switch the store state accordingly?

@kristianmandrup
Copy link

kristianmandrup commented Aug 29, 2022

routing-store.js

<script>
import { writable } from "svelte/store";
export const routing = writable({});
</script>

routing-config.js

<script>
import { routing } from "./stores.js";
import { beforeUrlChange, afterUrlChange } from "@roxi/routify"
$beforeUrlChange((event, route) => {
  routing.set({state: 'before', event, route})
})

$afterPageLoad((page) => {
  routing.update(data => ({...data, state: 'after', page}))
})
</script>

Perhaps using https://www.routify.dev/docs/helpers#is-changing-page

Then create some components that subscribe to the store and activate the transitions?

@kristianmandrup
Copy link

Note that in routify 3, they are called hooks, not helpers https://v3.ci.routify.dev/docs#guide/concepts/hooks

Elements on individual pages can be accessed via context and nodes https://v3.ci.routify.dev/docs#guide/concepts/nodes
Perhaps meta and preloading could be useful to tap into? https://v3.ci.routify.dev/docs#guide/concepts/meta https://v3.ci.routify.dev/docs#guide/concepts/preloading

@kristianmandrup
Copy link

I think the page transitions in this video could be an inspiration: https://www.youtube.com/watch?v=G3KFXKawy7Y&list=PLA9WiRZ-IS_zXZZyW4qfj0akvOAtk6MFS&index=18

Looks like a very neat design.

@Tommertom
Copy link
Owner Author

Tommertom commented Aug 30, 2022

Hi @kristianmandrup - thank you so much for this! I will study more carefully in the coming days/week - busy on something else (=J O B). But really appreciate the effort!

Ps. I want to move away from routify in favor of sveltekit router in spa mode

@Tommertom
Copy link
Owner Author

https://youtu.be/ua6gE2zPulw

Placeholder - Geoff Rich on element transitions

@kristianmandrup
Copy link

kristianmandrup commented Aug 30, 2022

By chance, I just discovered the new PageTransition API available in Chrome canary (ie native support).

https://www.youtube.com/watch?v=JCJUPJ_zDQ4 - Bringing page transitions to the web
https://hyva.io/blog/news/page-transition-api-at-the-hyvacamp-2022-hackathon.html

@kristianmandrup
Copy link

Oh, we discovered the same PageTransition API independently :)

@kristianmandrup
Copy link

Aha, here is a Svelte demo not using the Page Transition API - https://github.com/pngwn/svelte-travel-transitions

@kristianmandrup
Copy link

All the info for his presentation: https://geoffrich.net/posts/svelte-london-2022/
And here the repo for his demo: https://github.com/geoffrich/sveltekit-shared-element-transitions

I would think that his code would just need to hooked up to a wrapper around roxify router which provides slightly more information and flexibility.

@kristianmandrup
Copy link

kristianmandrup commented Aug 30, 2022

For Routify v3 the Router has an internal history of Routes

    /** @type {Route[]} */
    history = []

Hooks type defs

 * @typedef { function({route: Route}): any } BeforeUrlChangeCallback
 * @typedef { function({
 *   route: Route,
 *   history: Route[]
 * }): any } AfterUrlChangeCallback

Router class

init(input) {
        if (this.url.getActive()) {
            this.log.debug('router was created with activeUrl') // ROUTIFY-DEV-ONLY
            this._setUrl(this.url.getActive(), 'pushState', true)
        }
}

    /**
     *
     * @param {string} url
     * @param {UrlState} mode pushState, replaceState or popState
     * @param {boolean} [isInternal=false] if the URL is already internal, skip rewrite.toInternal
     * @param {Object=} state a state to attach to the route
     * @returns {Promise<true|false>}
     */
async _setUrl(url, mode, isInternal, state) {
        const { activeRoute, pendingRoute } = this
        // ...
        const route = new Route(this, url, mode, state)
        // ...
        pendingRoute.set(route)
        await route.loadRoute()
        // ...
}

Route class, calls beforeUrlChange with route, then pushes active route to router history, then calls afterUrlChange with route and history in reverse order

class Route {
    constructor(router, url, mode, state = {}) {
        this.router = router
        this.url = url
        this.mode = mode
        this.state = state
         // ...            
    }

    async runBeforeUrlChangeHooks() {
        return await this.router.beforeUrlChange.run({ route: this })
    }

    async loadRoute() {
        const { router } = this
        const pipeline = [
            this.runBeforeUrlChangeHooks,
            this.loadComponents,
            this.runGuards,
            this.runPreloads,
        ]

        this.loaded = new Promise(async (resolve, reject) => {
           // will run beforeUrlChange
          for (const pretask of pipeline) {
                // ...            
           })

           // ...

            const $activeRoute = this.router.activeRoute.get()
            if ($activeRoute) router.history.push($activeRoute)

            router.activeRoute.set(this)

            router.afterUrlChange.run({
                route: this,
                history: [...router.history].reverse(),
            })
          })
          return this.loaded
    }
}

@kristianmandrup
Copy link

According to Geoff's code we need the from and to routes. In the beforeUrlChange hook we get the route which contains the router with the history (where we can get from) and the url of the route being navigated to

@kristianmandrup
Copy link

@Tommertom Routify author has just provided this store for integration with Page Transition wrapper by @geoffrich .

$: store = {
  from: $activeRoute
  to: $pendingRoute 
  state: $pendingRoute ? 'started'  :  'complete' 
}

@kristianmandrup
Copy link

kristianmandrup commented Sep 3, 2022

I've just created this sample repo using latest Svelte, routify 3 and the Page Transition API wrapper by @geoffrich
https://github.com/kristianmandrup/page-transition-app

This could serve as a simple starting point to build on.

@Tommertom
Copy link
Owner Author

Hey there

So from the rapid reading I have done I see a few things

  • I will be moving to SvelteKit router, so removing Routify from the main repo. So that limits my personal appetite to invest in Routify.
  • the key challenge I see, and I am not sure if your solution repos solves it - is that the incoming page transition as well outgoing page transition needs all sorts of animating stuff going on INSIDE the pages. So not just the full page transitioning, but some default components (if present) to animate as well.

Example - if the outgoing page has a back button, that specific button itself, on top of the page animation, should animate in a bit different way - like defined in https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/transition/ios.transition.ts

So, I am just curious if that part is included in your setup - and if so, does the node in Routify (let's stick to Routify for this) really refer to the mounted components?

@kristianmandrup
Copy link

Hi @Tommertom, from my understanding and investigation, SvelteKit does not currently support Ionic apps. Hence I'm not sure if you can use the SvelteKit Router instead of Routify.

The example app by @geoffrich as demonstrated in his video showcases the back button and an image on a page being animated independently of the page itself during the page transition.

This fine-grained animation is supported in https://github.com/kristianmandrup/page-transition-app/blob/main/src/lib/page-transition.ts and https://github.com/kristianmandrup/page-transition-app/blob/main/src/lib/reduced-motion.ts in particular using

(If I recall correctly)

@Tommertom
Copy link
Owner Author

Sveltekit and Ionic (my lib that is) go well together as long as sveltekit runs SPA mode

I have it running - I just need to go to their latest breaking release and then implement it

@kristianmandrup
Copy link

Very cool :) SvelteKit sure moves fast...
Very exciting!

@Tommertom
Copy link
Owner Author

Closing this issue as I am moving the ionic package related stuff to the ionic-svelte npm package repo

If you haven't seen this - please check out ionic-svelte on npm. Or create your SvelteKit Ionic app via the CLI: npm create ionic-svelte-app@latest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants