Jump to content
Search Community

Vue & Gsap list animation: List elements not reset to original position when re-rendered

Robert Wildling test
Moderator Tag

Recommended Posts

Codesandbox (didn't know how to reproduce it on Codepen, sorry!)

https://codesandbox.io/s/vue-and-gsap-listanimation--lhvk1

 

Hi, all!

 

I am not sure at all if I my problem is GSAP or Vue-related. If this is the wrong place, please let me know! 

 

The demo, that uses GSAP within Vue, shows a list (a menu) that stagger-animates into view. This happens on initial page load, in `Start.vue`, which is the first component that is loaded by the Vue router.
In the `beforeRouteEnter` navigation guard, within "next", the elements of the menu are collected, then gsap sets some x and y values (moving the items to the top left) and eventually the animation starts. 
The tween is assigned to the variable `vm.tween` in order to be able to kill it should a user be too fast and choose a menu before the animation stopped.

 

beforeRouteEnter(to, from, next) {
    next((vm) => {
      vm.$forceUpdate();

      const menu = vm.$refs.menu;
      const menuItems = menu.getElementsByTagName("li");

      gsap.set(menuItems, { autoAlpha: 0, y: -32, x: -48 });

      // Assign to var in order to be able to kill it later
      vm.menuTween = gsap.to(
        menuItems,
        {
          autoAlpha: 1,
          duration: 1.5,
          ease: "elastic",
          stagger: 0.09,
          x: 0,
          y: 0,
          force3D: true,
          onComplete: next,
        },
        0.65
      );
    });
  },

So much for the onload animation.

 

After clicking a menu, an animation starts to fade out and "drop" the menu items step-by-step. After that there is a lot of killing going on in "resetNav", before the new page loads.
 

  beforeRouteLeave(to, from, next) {
    const menu = this.$refs.menu;
    const menuItems = menu.getElementsByTagName("li");

    this.menuTween.kill();

    gsap.to(menuItems, {
      autoAlpha: 0,
      duration: 0.1,
      ease: "power2.in",
      stagger: -0.05,
      x: 32,
      y: 16,
      force3D: true,
      onComplete: this.resetNav,
      onCompleteParams: [next],
    });
  },
    
  methods: {
    resetNav(next) {
      this.menuTween.kill();
      gsap.killTweensOf(this.menuTween);
      gsap.set(this.menuTween, { clearProps: "all" });

      next();
    },
  },

So far so good.

 

But now come the troubles: When I click on the "Back to Home link" link, I would expect that the onload animation would be shown again. But instead nothing happens. And I have no idea why! All those killings should have removed any inline styles and repositioned the items. Which is probably a useless step anyway since the component is deleted.


What is wrong with this code?



 

See the Pen by (@) on CodePen

Link to comment
Share on other sites

Hi,

 

Well your setup is quite unusual I must say, but the main issue lies in this lines:

resetNav(next) {
  this.menuTween.kill();
  gsap.killTweensOf(this.menuTween); // PROBLEM HERE
  gsap.set(this.menuTween, { clearProps: "all" }); // PROBLEM HERE
  next();
},
  
beforeRouteEnter(to, from, next) {
  next((vm) => {
    // Assign to var in order to be able to kill it later
    vm.menuTween = gsap.to(
      menuItems,
      {/**/},
      0.65 // PROBLEM HERE
    );
  });
},

In the resetNav method, you're use killTweensOf and then you're trying to clear the applied styles using clearProps on a GSAP instance. The killTweensOf() method works on the DOM node or object GSAP is actually tweening, in this case a list of DOM nodes. You should pass the collection of the nav elements to this method in the onComplete callback.

 

Then in the beforeRouteEnter hook you're passing an extra parameter to a GSAP instance that is not a timeline, so basically there is no use for that there. Just remove that.

 

This code seems to do what you're after:

methods: {
  resetNav(next, menuItems) {
    this.menuTween.kill();
    gsap.set(menuItems, { clearProps: "all" });

    next();
  },
},
beforeRouteEnter(to, from, next) {
  next((vm) => {
    vm.$forceUpdate();

    const menu = vm.$refs.menu;
    const menuItems = menu.getElementsByTagName("li");

    gsap.set(menuItems, { autoAlpha: 0, y: -32, x: -48 });

    // Assign to var in order to be able to kill it later
    vm.menuTween = gsap.to(
      menuItems,
      {
        autoAlpha: 1,
        duration: 1.5,
        ease: "elastic",
        stagger: 0.09,
        x: 0,
        y: 0,
        force3D: true,
        onComplete: next,
      }
    );
  });
},
beforeRouteLeave(to, from, next) {
  const menu = this.$refs.menu;
  const menuItems = menu.getElementsByTagName("li");

  this.menuTween.kill();

  gsap.to(menuItems, {
    autoAlpha: 0,
    duration: 0.4,
    ease: "power2.in",
    stagger: -0.1,
    x: 32,
    y: 16,
    force3D: true,
    onComplete: this.resetNav,
    onCompleteParams: [next, menuItems],
  });
},

Finally there is no need to add the GSAP instance as a reactive property in the Vue instance, you can attach it using this.menuTween or vm.menuTween

 

Happy Tweening!!!

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Thank you very mich, Rodrigo! – My "unusual setup" is a result of simply not knowing it better :-). It turns it that it was the wrong parameter (which I thought is meant for "delay"). Removing that is enough to make it work, there is no need for $forceUpdate neither the "resetNav" hack and not even gsap's reset property and kill() methods.
I updated the codesandbox example. It should no be less unusual, right?

 

I am not sure, though, what you mean by not adding the GSAP instance as a reactive property. Are you referring to the "this.menuTween" property? If so: I did that, because it is theoretically possible that a menu can be clicked while the onload animation is still going on. And to catch that problem, I need a reference to the tween that is outside of Vue's navigation guard functions. That messes up the "fade out" animation... or do I misunderstand you?

Link to comment
Share on other sites

Hi,

 

What I meant is that is not entirely recommended adding a GSAP instance to the object returned by the data method. Mostly because GSAP shouldn't have any effects on the component's reactivity. Is better to simply attach it to the Vue instance using either this.menuTween or vm.menuTween. You can still access the property using the this keyword anywhere in your app. Basically your data method should look like this (based on the codesandbox sample):

data() {
  return {
    menuItems: [
      /* Array Elements */
    ],
  };
},

But, as I mentioned above, you can still attach the GSAP instance to the component:

beforeRouteEnter(to, from, next) {
  next((vm) => {

    // Assign to var in order to be able to kill it later
    vm.menuTween = gsap.to(/*...*/);
  });
},
  
// Or using mounted
mounted() {
  this.menuTween = gsap.to(/*...*/);
}

You can read more about it here:

https://vuejs.org/v2/guide/instance.html#Data-and-Methods

 

I also recommend you to take a look at this resources, in order to learn more about Vue. Brad Traversy has a lot of free stuff in many subjects and has a couple of new Vue courses here:
https://www.youtube.com/c/TraversyMedia/videos

 

Also Erik Hanchett is a big Vue evangelist and has a ton of Vue stuff:

https://www.youtube.com/c/ProgramWithErik/videos

 

Happy Tweening!!!

  • Like 2
Link to comment
Share on other sites

Hmmm... I still do not see the place where I add a gsap instance to the object returned by the data object... My methods look like you recommend and I also attach gsap to vm.menuItems / this.menuItems (depending on the life cycle).

 

Where do I need glasses? Sorry for being so short-sighted...

Link to comment
Share on other sites

In your Start.vue file:

data() {
  return {
    menuItems: [...],
    menuTween: null, // this is replaced by the GSAP instance
  };
},

After this, everytime you do either of these:

this.menuTween = gsap.to();
vm.menuTween = gsap.to();

The reactive property is updated and triggers an unnecessary render of the component, because nothing in the component actually depends on that particular property being updated.

 

Happy Tweening!!!

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

1 hour ago, Robert Wildling said:

Hmmm... I still do not see the place where I add a gsap instance to the object returned by the data object...

 

He's saying you don't need to put anything related to GSAP on the data object. The data object is for reactive properties. GSAP animations are not reactive, and therefore you are putting additional overhead into your components by adding your animations to the data object.

 

You can set your animations in any method, mounted, or from the vm passed in. It all points to the same instance.

// this isn't needed
data() {
  return {};
},

beforeRouteEnter(to, from, next) {
  next((vm) => {

    vm.foo();
    console.log(vm.bar); // Hello
  });
},
  
// Or using mounted
mounted() {
  this.foo():
  console.log(this.bar); // Hello
},
methods: {
  foo() {
    this.bar = "Hello";
  }
}

 

  • Like 1
Link to comment
Share on other sites

As Blake points, you can.

 

Vue is smart enough, so when you create a reference in the vue instance you can reach it anywhere in your code using the this keyword. For example you can create a method and call that method anywhere in your code using this.myMethod(), but myMethod is not in the data object. Same thing with the mounted  method, any property you attach to this will be reachable anywhere where this points to the Vue instance.

 

1 minute ago, OSUblake said:

Did you try it?

Agree, please give it a try

  • Like 2
Link to comment
Share on other sites

I very much appreciate your help, Rodrigo and Blake! Really! And I apologise for standing too close to the tree and not seeing the forrest! 
 

Could it be that you just want to tell me "don't define menuTween on the data object, instead just declare it in methods"?

 

(I can promise you that I do nothing else but trying and testing since days on this scenario... I just removed the menuTween from data, and yes, as you both said: it still works :-) – But I am still unsure if this is what you mean...)

Link to comment
Share on other sites

17 minutes ago, Robert Wildling said:

Could it be that you just want to tell me "don't define menuTween on the data object, instead just declare it in methods"?

 

Yep. The main point is that you can define your animations anywhere. It doesn't have to be inside a method per se. The only bad place might be inside created as you won't be able to access any refs at that point in the lifecycle.

 

 

  • Like 2
  • Thanks 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...