Transition: 'enter' runs twice (using requestAnimationFrame)

Hi, this is my first time on the Vue forum!

I’ve been using requestAnimationFrame and vue-router to create some expanding & contracting page transitions:


More info and code here

They seem to be working as expected. However, I also want to use simpler ‘slide’ transitions in other parts of the website: the old page slides to the left, while the new page slides in from the right. As I’m wrapping my site’s whole <router-view> with my page transition component, I’m using the same approach for the slide transitions as I used for the ‘expand’ transitions.

For the above example, I’m using mode="in-out" because the new page should appear before the old page leaves. For the ‘slide’ transitions, I want to use the default (simultaneous) transition mode.

When I tried this, the pages would first animate correctly, and then when the leave hook finished, the requestAnimationFrame loop in enter would start from zero again.

I made a simpler version of the code (without vue-router) to show the issue more clearly, just using a couple of divs. Try switching between mode="out-in", mode=“in-out”, and mode="":

I’ve set a really long duration so that you can see the leave and enter methods happening in the console. (“pink leaving”, “blue entering”, “pink left”, “blue entered”)

When I log the progress of my rAF loop in the enter method, I can see where the problem occurs:

...
0.9813333333333333 
0.987 
0.9923333333333333
pink left 
0 
0.006 
0.012
...

It looks to me like the leave method finishing is somehow stopping the enter method from completing, and starting progress from zero again. But I don’t know how to fix the issue.

This is my first time using requestAnimationFrame, so I may have missed something obvious. Any ideas?

Update: I got some help from friendly folks on Secure Scuttlebutt!


The issue was that both my enter and leave methods were using the same trackTime function and startTime data property, at the same time.
Making this.startTime an object which could track start times for each method seemed to fix the issue. I just needed to add an extra argument (the name of the method I’m tracking) when I call getProgress and trackTime.

data

startTime: {},

methods

enter (element, done) {
  const tick = () => {
    const progress = this.getProgress('enter')
    const offset = true
    this.calculateCurrentValues(progress, offset)
    this.applyTransformValues(element)
    if (progress === 1) {
      return done()
    }
    requestAnimationFrame(tick)
  }
  tick()
},
leave (element, done) {
  const tick = () => {
    const progress = this.getProgress('leave')
    this.calculateCurrentValues(progress)
    this.applyTransformValues(element)
    if (progress === 1) {
      return done()
    }
    requestAnimationFrame(tick)
  }
  tick()
},
trackTime (action) {
  const now = performance.now()
  if (this.startTime[action] == null) {
    this.startTime[action] = now
  }
  const elapsed = now - this.startTime[action]
  if (this.duration != null && this.duration <= elapsed) {
    this.startTime[action] = null
  }
  return elapsed
},
getProgress (action) {
  return this.duration > 0 ? Math.min(this.trackTime(action) / this.duration, 1) : 1
},

I don’t think this is necessarily the cleanest solution, as I’m still running two separate requestAnimationFrame loops simultaneously, which doesn’t make much sense from a performance perspective.

But at least now I’ve got slide transitions working together with expand transitions!