Jump to content
Search Community

Vue 3 (compostion api with script setup) and timeline.reverse() in vue-route navigaition guard

Robert Wildling test
Moderator Tag

Recommended Posts

Hi, GSAPlers!

 

Let me start by apologizing for not providing a codepen. I do not know how to setup a vue 3 / vite project on codepen. (I think this is only possible in the pro version?) And  I spent my day so far to try to get a codesandbox working, but to no avail (I have to admit that I have never been successful on codesandbox).

 

All I can do is provide a github link for those who are willing to download and have a look into it on their local machine (which, of course, i do not expect, but at the moment it is the most I can do):

https://github.com/rowild/jvds-vue3

 

That said I think the problem itself might not even need a codepen. This is what I have: 

 

In a Vue 3 Composition API ScriptSetup & Vite & GSAP project, a timeline is created. Items are added to the timeline in the `onMounted` hook and eventually `play`ed. The result - appearing letters - works fine.


Once the enter button is clicked, a new route is called. But before that the timeline should be played in reverse (letters should disappear), which to my knowledge happens in a "Vue Router Guard", `onBeforeRouteLeave`, which comes with 3 params (`to, from, next`). `next` would eventually fire the route change, and it should be called, when the animation is completed, in this case, when `onReverseComplete` is fired.

 

// <script setup>
import { ref, onMounted } from 'vue'
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import gsap from 'gsap'

// [...]

const tl = gsap.timeline({
  paused: true,
  onComplete: () => { console.log('TL onComplete invoked')},
  onCompleteScope: this
})

/* Life cycles hooks */

onMounted(() => {
  tl.set('.letter', { opacity: 0 })
  tl.set('.line', { opacity: 0, scaleY: 0 })
  tl.set('.intro-container', { autoAlpha: 1 })
  tl.to('.letter', { autoAlpha: 1, duration: 0.5, stagger: 0.035 }, 0)
  tl.to('.line', { opacity: .7, scaleY: 1, duration: 1.25 }, 0.25)
  tl.to('.btn-ui-show', { autoAlpha: 1, duration: 0.5 }, '>-.5')
  tl.play()
})

/* Router Guards */

onBeforeRouteLeave((to, from, next) => {
  console.log('IndexPage onBeforeRouteLeave');
  
  tl.eventCallback('onReverseComplete', console.log('reverse complete'))
  tl.timeScale(1)
  tl.reverse(0)
})

 

However, I have no idea how to properly implement that event callback. While the reverse.played animation works fine, the callback in the shown solution is called right away instead of at the end of the animation. Assigning the `onReverseCallback` in the timeline setup (where there is also `onComplete`) won't work, because there is no access to the router guard's `next()` function.

 

How can I solve this problem? Thank you!

 

See the Pen by (@) on CodePen

Link to comment
Share on other sites

You need to check the Vue3 Docs for Route Transitions. You can create a Leave Transition as a function and place it in methods: { here }. And use the Javascript hooks it shows at the bottom of the page ---> onBeforeLeave: function (el) { your code here for the leave transition}. You could just build your reverse animation there and it will run when you leave the page. https://vuejs.org/guide/built-ins/transition.html#javascript-hooks

  • Like 1
Link to comment
Share on other sites

Building a separate animation for the "onLeave" animation is one way to go and would solve the callback problem. However, I would like not to create extra code and instead use the existing timeline in `reverse` mode. But, as mentioned, this does create a callback problem, for which I am seeking a solution...

Link to comment
Share on other sites

I think you problem is more of Vue problem than GSAP. If you have a Button "Enter" and want to reverse the Animation before going to the next page. You could simple create a eventlistener on that Button run the tl.reverse and onComplete run nav to the page you want. It is far more simple than trying to complicate routing. With Vue you can create your custom transitions or use routing methods as Detailed in their Docs. With Gsap you create any of the animations. I added a regular tween example from a Vue project and you can insert your timeline or reference the timeline and call tl.reverse()

document.querySelector('#button')
      .addEventListener('click', (e) => {
        e.preventDefault()
        gsap.to('#app', {
          opacity: 0
        })
        gsap.to(camera.position, {
          z: 25,
          ease: 'power1.inOut',
          duration: 2
        })
        gsap.to(camera.rotation, {
          x: 1.57,
          ease: 'power1.inOut',
          duration: 2
        })
        gsap.to(camera.position, {
          y: 1000,
          ease: 'power1.in',
          duration: 1,
          delay: 1.5,
          onComplete: () => {
            this.$router.push('/page')
          }
        })
      })
  • Like 1
Link to comment
Share on other sites

@Robert Wildling Perhaps it's not my place, but I would advise against trying to get everything done with one tween or timeline unless its expensive to calculate it. It's almost never worth the effort and, in my experience, the source of many headaches compared to biting the bullet with an extra animation. You can always go back and tidy up the gsap if somehow it becomes too heavy, but I can't imagine that being the case here.

To answer your question, pause the TL, reverse it, manually move the playhead to the end of the timeline. and then play it. This should avoid the onReverseComplete function from firing immediately.
 

  • Like 1
Link to comment
Share on other sites

In my experience Router Guards are to protect routes from unauthorized users. Like if you have an App with Authentication where a user needs to Login and once logged in they have access to pages to Post or make comments. I think your case you could run code in onBeforeLeave but in the Vue 3 Docs onBeforeLeave is run from methods: { onBeforeLeave }. You have it isolated in your code and most likely why Vue is not running it. It shows the code example in the Vue 3 documents.  I think you may be confusing page transitions with routing. 

export default {
  // ...
  methods: {
    // called before the element is inserted into the DOM.
    // use this to set the "enter-from" state of the element
    onBeforeEnter(el) {},

    // called one frame after the element is inserted.
    // use this to start the animation.
    onEnter(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the enter transition has finished.
    onAfterEnter(el) {},
    onEnterCancelled(el) {},

    // called before the leave hook.
    // Most of the time, you shoud just use the leave hook.
    onBeforeLeave(el) {},

    // called when the leave transition starts.
    // use this to start the leaving animation.
    onLeave(el, done) {
      // call the done callback to indicate transition end
      // optional if used in combination with CSS
      done()
    },

    // called when the leave transition has finished and the
    // element has been removed from the DOM.
    onAfterLeave(el) {},

    // only available with v-show transitions
    leaveCancelled(el) {}
  }
}

 

  • Like 1
Link to comment
Share on other sites

@tomcatbuzz I appreciate your feedback, but, as written above, I am not using API mode, but script setup, and the things you refer to (onLeave etc) have to be used on a page, where the route-view is defined (a layout page, e.g.). The code I have to deal with, however, lives in a component that is loaded and unloaded. Meaning the router-view does not have access to that component's DOM (well, it does at some point, but that would make things really complicated).

 

It is not a difficult or unusual setup and it is totally ok to be done on router guards. All that I want to know (and where I obviousely make thinks complicated, even though it shouldn't be, since it would be short, very readable and performant code) is if there is a way to call a router guard's "next()", when using "tl.reverse()" on a "globally" defined "tl".

 

@SteveS Thank you for your input! I have not tried yet what you suggested (pause, reverse...), but shouldn't "tk.reverse(0)" - "0" being the indicator to play the tl from the very end - do all that automatically?

When the animation starts, it is only played forward, so a"onReverseComplete" has not yet been fired... unless something else it happening that I do not know about... 

 

UPDATE:
@SteveS Indeed, your suggestion is the solution. Added this:

 

onBeforeRouteLeave((to, from, next) => {
  tl.pause()
  tl.reversed()
  tl.eventCallback('onReverseComplete', () => {
    console.log('reverse complete')
    next()
  })
  tl.timeScale(2).reverse(0)
}

Thank you! 👍

  • Like 2
Link to comment
Share on other sites

I'm not sure. It's possible that reversing it reverses the event firing order as well since the documentation says it is the same thing that is done to make a .from() tween a .to() tween, in which case perhaps it will fire with the onComplete event. Manually doing it ensures you don't have to think about it.

  • Like 1
Link to comment
Share on other sites

Just one little side note - I noticed this: 

tl.eventCallback('onReverseComplete', console.log('reverse complete'))

And you said that was firing right away, but that's because you were executing the console.log() immediately, and passing the RESULT of that as the event callback :)
 

I think you meant to do this: 

tl.eventCallback('onReverseComplete', () => console.log('reverse complete'))

Right?

  • Like 1
Link to comment
Share on other sites

8 minutes ago, GreenSock said:

I think you meant to do this: 

tl.eventCallback('onReverseComplete', () => console.log('reverse complete'))

Right?

 

Yes, Jack, you are totally right! I actually wanted to mention that, but than I was so overwhelmed with the result that I forgot all about it! Thanks for bringing it up! 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...