Jump to content
Search Community

Awaiting for timelines to revert()

m__shum test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

Hi all, I'm working with SplitText and I need to be able to revert everything, reset timelines, and then recreate and re-run everything on resize. 

The main issue I'm having is chaining everything nicely. timeline.reverse() returns itself 'for easy chaining' according to the docs. Except you can't chain timeline.reverse().then() (this throws an error) and wrapping it in a promise doesn't work either.

What is the best way to make sure everything has been fully reverted when wrapping reverts in external functions? Because if I dump everything into one function, things do get cleaned up correctly, but we obviously don't want to do that.

See the Pen WNWaqJM by m-shum (@m-shum) on CodePen

Link to comment
Share on other sites

Hi,

 

I'm not 100% sure of what issue you're experiencing, but waiting for 1 tick of GSAP's ticker should be enough. As far as I can tell, after you revert your GSAP instances and create the new ones, it should take 1 tick to render the new ones, so that should be enough. Based on your demo this is how I would proceed:

const tweenable = document.getElementById('tweenable')

let tween, timer;

const createTween = () => {
   tween = gsap.timeline().fromTo(tweenable, {opacity: 1}, {opacity: 0, duration: 1});
};

const revertTween = () => {
  tween && tween.revert();
  createTween();
};

window.addEventListener("resize", () => {
  timer && clearTimeout(timer);
  timer = setTimeout(() => {
    revertTween();
  }, 200);
});

createTween();

Here is a fork of your demo:

See the Pen oNOVzdg by GreenSock (@GreenSock) on CodePen

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

Yeah, I don't understand what you're trying to do there, @m__shum. Reverting happens immediately, there's no need to wait for a tick. But you mentioned reverse() (which isn't in your demo) - are you trying to have an animation play in reverse and then after it's done (playhead reaches the start), you want to revert() it? If so, just use onReverseComplete to fire off a function that reverts. 

 

It'd be super useful if you provided a minimal demo showing exactly what the problem is. Maybe I'm missing something obvious in your original demo there. 

Link to comment
Share on other sites

6 hours ago, GreenSock said:

Yeah, I don't understand what you're trying to do there, @m__shum. Reverting happens immediately, there's no need to wait for a tick. But you mentioned reverse() (which isn't in your demo) - are you trying to have an animation play in reverse and then after it's done (playhead reaches the start), you want to revert() it? If so, just use onReverseComplete to fire off a function that reverts. 

 

It'd be super useful if you provided a minimal demo showing exactly what the problem is. Maybe I'm missing something obvious in your original demo there. 

That's my fault, I tried to narrow down my issue as much as possible to simplify it and obviously the context was important!


The real issue I'm having is actually related to splittext and resizing in Nuxt, and I'm unable to include splittext in a demo since it's a club plugin and I can't for the life of me figure out how to set up a nuxt environment in codepen. 

What I would like to do is this:
1. On resize, immediately reset the split to its original state so that the text resizes 'naturally'. 
2. Debounce re-creating the timeline 
3. Set the timeline's progress to 1 so the whole thing looks like it never happened. 

Then, when the user goes on their merry way interacting with this element, it animates as it's supposed to, having resized and done all its business.

function createTimeline() {
  split = new SplitText('.text', { type: 'lines' })
  timeline = gsap.timeline({ paused: true, defaults: { delay: 0.15 } })
    .fromTo(split.lines, { y: '100%', x: 10, autoAlpha: 0, scale: 0.99 }, { duration: 1, y: 0, x: 0, autoAlpha: 1, scale: 1, stagger: 0.07, ease: 'power2.inOut' })
}

function recreateTimeline() {
    createTimeline()
    timeline.progress(1)
}

const debounceHandler = debounce(recreateTimeline, 300)

function handleResize() {
  if(timeline){
      timeline.revert()
      split.revert()
  }
  debounceHandler()
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})


What ACTUALLY happens is probably an issue with Nuxt more than anything (or maybe I'm missing something!), but it's the reason I thought I had to wait for .revert().
When I run handleResize(), the timeline and split are reverted and the debounceHandler is called just fine. The problem is that when recreateTimeline() calls createTimeline(), the split is not properly reverted and SplitText() runs on text that's already been split. Even though it should've been reset to its original state when handleResize() was first called. It's like the reverts never happened, or if they did they got stuck halfway. The timeline doesn't recreate properly and none of the animations run after resize. This does not happen if I debounce the entire handleResize function but I don't want to do that because when the user resizes the window down, they will see the incorrectly positioned text for 300ms or however long I set the debounce to. 

What I ended up having to do is this horrorshow:
Basically I'm having to call the reset behavior multiple times to make sure it's cleaned up even though the reset DOES happen immediately, but for reasons I don't yet understand, not correctly or fully.  

let timeline
let split

function createTimeline() {
  split = new SplitText('.text', { type: 'lines' })
  timeline = gsap.timeline({ paused: true, defaults: { delay: 0.15 } })
    .fromTo(split.lines, { y: '100%', x: 10, autoAlpha: 0, scale: 0.99 }, { duration: 1, y: 0, x: 0, autoAlpha: 1, scale: 1, stagger: 0.07, ease: 'power2.inOut' })
}

function destroyTimeline() {
  timeline?.revert()
  split?.revert()
  split = null
  timeline = null
}

function recreateTimeline() {
  destroyTimeline()
  nextTick(() => {
    createTimeline()
    timeline.progress(1)
  })
}

const debounceTimeline = debounce(recreateTimeline, 300)

function handleResize() {
  timeline?.revert()
  split?.revert()
  debounceTimeline()
}

onMounted(() => {
  window.addEventListener('resize', handleResize)
})


If you're still reading by this point, I salute your immense patience. 

Basically I thought that MAYBE the issue was that reverts() weren't immediate, and I had to await them before running anything that relied on them being complete.

Link to comment
Share on other sites

1 hour ago, Rodrigo said:

Hi,

 

You can use Stackblitz to create a Nuxt demo and the GSAP Trial package in order to use the bonus plugins on Stackblitz:

https://stackblitz.com/

https://www.npmjs.com/package/gsap-trial

 

Here is a simple Nuxt demo:

https://stackblitz.com/edit/nuxt-starter-vzsxyp?file=app.vue

 

Hopefully this helps.

Happy Tweening!

Thank you SO much Rodrigo, this is so much cleaner than my mess, and works beautifully!

 

I do have a (possibly stupid) question about your demo – in the debounce function you call timer && clearTimeout(timer) and in CreateTween you also call completed && tween.progress(1). Just curious what this does – are you returning a boolean value by doing this? I haven't seen this syntax before.

Again a huge thank you, I've been banging my head against the proverbial wall for two days with this.

Link to comment
Share on other sites

No problemo! Also in these forums there are no stupid questions 👍

 

That's just the Logical AND operator:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND

 

Basically it checks if timer is truthy, if it is it'll clear the timeout. By default the timer variable is undefined which will return falsy and the clearTimeout won't be executed, same with the completed boolean. I want to set the progress of the tween to 1 only if the tween has run completely.

 

Hopefully this clear things up.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

4 hours ago, Rodrigo said:

No problemo! Also in these forums there are no stupid questions 👍

 

That's just the Logical AND operator:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND

 

Basically it checks if timer is truthy, if it is it'll clear the timeout. By default the timer variable is undefined which will return falsy and the clearTimeout won't be executed, same with the completed boolean. I want to set the progress of the tween to 1 only if the tween has run completely.

 

Hopefully this clear things up.

Happy Tweening!

Thanks for clarifying, I'll definitely be using this syntax in the future!

If this isn't too far out of the scope of my initial issue, I managed to reproduce something I'm running into with these tweens. I have two instances of the same component (it's all in the demo) – one on the page and one inside a modal. For their timelines to run independently I need to declare them as Vue refs. Which is fine, except for some reason when the timeline is reset on reside, the timeline for the other instance of the component gets messed up. 

For example in this demo, scroll down to activate the on-page animation, resize it, scroll again to re-activate it, then toggle the modal. The modal splitText animation doesn't run correctly, probably because the split isn't properly recalculated. I suspect it's a vue-specific issue but if there's any way to negate it I'm all ears!

Here's the demo: https://stackblitz.com/edit/nuxt-starter-zdudyt?file=app.vue,components%2FTextComponent.vue

Link to comment
Share on other sites

Mhh... Why are you using intersection observer for something that can be done with ScrollTrigger?

 

I think you are overcomplicating this quite a bit. If I was you  I'd create an extra prop for ScrollTrigger and if that prop has a ScrollTrigger configuration, you just need to check if is not falsy, instead of using just SplitText use ScrollTrigger to handle when the animation plays, something like this:

See the Pen abxMRgp by GreenSock (@GreenSock) on CodePen

 

I updated the demo I posted before so it uses ScrollTrigger

https://stackblitz.com/edit/nuxt-starter-vzsxyp?file=app.vue

 

The only detail is that this check is not needed with ScrollTrigger:

completed && tween.progress(1);

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

2 hours ago, Rodrigo said:

Mhh... Why are you using intersection observer for something that can be done with ScrollTrigger?

 

I think you are overcomplicating this quite a bit. If I was you  I'd create an extra prop for ScrollTrigger and if that prop has a ScrollTrigger configuration, you just need to check if is not falsy, instead of using just SplitText use ScrollTrigger to handle when the animation plays, something like this:

 

 

 

I updated the demo I posted before so it uses ScrollTrigger

https://stackblitz.com/edit/nuxt-starter-vzsxyp?file=app.vue

 

The only detail is that this check is not needed with ScrollTrigger:

completed && tween.progress(1);

Hopefully this helps.

Happy Tweening!

That's so much better – I completely forgot about ScrollTrigger! You've been insanely helpful!

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...