Vue router in-out page transition: transition in a new route while old route remains visible

To illustrate what I’m trying to achieve but also discuss and learn about each mechanism separately, I split the issue into two independent challenges:

1. Keep previous route visible until new route has transitioned in

Whether the transition is sliding, what I’m trying here, or just fading; mode in-out doesn’t work as I would expect it, namely that the existing route stays visible until the next route has finished its transition (e.g. overlaid itself over the previous one), exactly as illustrated here in the last example of this section https://vuejs.org/v2/guide/transitions.html#Transition-Modes, showing two buttons with in-out mode. Instead no transition is happening but it just flips the routes statically at half of the given transition time.

Is there any caveat with routes and an obvious reason why this wouldn’t work the same way, e.g. that a single router-view can only hold one at the time and therefore in-out is not possible?

EDIT 1:

I figured out that in-out would actually only work with position:absolute on both elements, otherwise they will not overlay. Any idea how I could elegantly include such a behavior, potentially setting that absolute position during router-transition only?

Current hack that has the visual slide-up modal effect (mode: in-out) I’m looking for: adding style="position:absolute; z-index:2100" to the dialog route. Then I would need to change the underlying transition once it’s shown in order to have the reverse hide effect (mode: out-in).

Also see EDIT 2 below.


2. Creating a modal-like page (route) which opens above another existing page when navigated to

I tried to hack that behavior by adding a second router-view in App.vue

<router-view />
<router-view name="dialog" />

The particular component is added to my routes like this

{
    path: 'records/new',
    components: {
      dialog: () => import('layouts/NewRecord.vue')
    },
    children: [
      {
        name: 'new-record',
        path: '',
        component: () =>
          import('src/pages/NewRecord.vue')
      }
    ]
  }

I’m not sure whether this approach even makes sense but I couldn’t make it work properly. The aim would be to just overlay another router-view name="dialog whenever a “dialog”-path is pushed, so while it can be animated (slide-up) the other router-view stays visible below. In the end I guess I’m facing the same issue here: once the route changes, the initial router-view discards its component because the path does not match the current location anymore.

Either way, there are people out there with more experience and expertise so I hope I could illustrate what I’m trying to achieve and I’m just curious and thankful to read your inputs.

EDIT 2

I could make it work the way I wanted with simply one <router-view> like in Part 1, wrapped in a custom page-transition component. It is quite a hack though AND I needed to add position: absolute to may page-layouts, to all of them actually (both the “leaving” and the “entering” component need position: absolute) when showing the dialog component. I don’t feel fully comfortable adding an absolute position to my page-layouts but so far there are no visual issues/bugs. Still I’m sure there’s a better way and I would love to hear suggestions.

Custom page-transition component:

<template>
    <transition :name="name" :mode="mode">
        <slot/>
    </transition>
</template>

<script lang="ts">
  import { Component, Watch } from 'vue-property-decorator'
  import Vue from 'vue'
  import { Route } from 'vue-router'

  @Component({
    components: {}
  })
  export default class PageTransition extends Vue {
    NAME_FADE = 'fade'
    NAME_SLIDE_UP = 'slide-up'
    NAME_SLIDE_DOWN = 'slide-down'
    MODE_OUT_IN = ''
    MODE_IN_OUT = 'in-out'

    name = this.NAME_FADE
    mode = this.MODE_OUT_IN

    @Watch('$route', { immediate: true, deep: true })
    onRouteChanged(newVal: Route, oldVal: Route) {
      if (newVal.meta.transition === 'dialog') {
        this.name = this.NAME_SLIDE_UP
        this.mode = this.MODE_IN_OUT

      } else if (oldVal && oldVal.meta.transition === 'dialog') {
        this.name = this.NAME_SLIDE_DOWN
        // shift next page in immediately below dialog
        this.mode = this.MODE_IN_OUT

      } else {
        // default
        this.name = this.NAME_FADE
        this.mode = this.MODE_OUT_IN
      }
    }

  }
</script>

<style lang="scss" scoped>
  .fade-enter, .fade-leave-to {
    opacity: 0;
  }
  .fade-enter-active, .fade-leave-active {
    transition: all 0.1s ease;
  }


  // start of enter element
  .slide-up-enter {
    transform: translateY(60%);
    opacity: 0;
  }
  .slide-up-enter-active {
    transition: all 0.3s ease-out;
    z-index: 2100;
  }

  // start of leave element
  .slide-up-leave, .slide-up-leave-active {
    opacity: 0;
  }
  

  // start of leave element
  .slide-down-leave {
    z-index: 2100;
  }
  .slide-down-leave-to {
    transform: translateY(60%);
    opacity: 0;
    z-index: 2100;
  }
  .slide-down-leave-active {
    transition: all 0.3s ease-in;
  }

  // start of enter element
  .slide-down-enter {
    opacity: 0;
  }
  .slide-down-enter-active {
    /* show immediately behind existing page (lower z-index) */
    transition: all 0s;
  }
</style>