Search code examples
vue.jsvuejs3requestanimationframe

TransitionGroup lag when using requestAnimationFrame


Description

I'm creating the following app logic:

  • There is a list of checkable items with a specific price for each.
  • There is a calculated total price for checked items.

I'm also animating the number change for the total price using the requestAnimationFrame. So far so good, the number animation itself works fine.

Problem

However, my TransitionGroup move transition starts to lag when I have the following element structure:

<Transition>
  <div>
    {{ animatedNumber }}
    <TransitionGroup>
      ...
    </TransitionGroup>
  </div>
</Transition>

Notes

  • Seems that there is some conflict between the Transition elements and requestAnimationFrame.
  • The TransitionGroup move animation works fine if I remove the parent Transition element.
  • The TransitionGroup move animation works fine if I remove the number counter based on requestAnimationFrame.
  • This issue happens only on Chrome.

Demo

https://stackblitz.com/edit/vitejs-vite-a4hnef?file=src/components/App.vue


Solution

  • To test the performance of your app. You can follow the following steps:

    Open chrome dev tool -> performance -> click record -> make animation by checking to a checkbox -> click stop
    

    The result should look like that:

    enter image description here

    Zooming into a task you will see:

    enter image description here

    As we can see each task is caused by the componentUpdateFn. That is because, in every animation frame, you update the variable and force Vue to re-render the template. It's too much work. To make your animation smooth, each animation frame should run in a 16ms time frame (60fps)

    To solve your problem you should put in less work as possible in each animation frame by:

    • optimizing your animate function
    • if you want to update your view in every frame, you should do it by pure JS instead of using Vue

    This is the optimal version of your code. I just use JS to update the animating number. It's not perfect but it much better than the original version.

    // in the script. update the view by pure JS
    watch(animatedTotalPrice, () => {
      totalPriceElm.value.innerText = animatedTotalPrice.value;
    });
    // in the template, DON'T let Vue do the updating work
    <h3 ref="totalPriceElm" v-once>{{ animatedTotalPrice }} USD</h3>