Jump to content
Search Community

Adding a new tween to a "played" timeline and .play() it again

Denis Gonchar test
Moderator Tag

Recommended Posts

Please, see the CodePen.

 

I created a timeline and put 2 tweens there:

  1. From initial to 100px
  2. Gap (1 sec)
  3. From previous (100px) to 200px

 

Then I .play() the timeline and after it completed I added a new tween right after the first tween, now it looks as following:

  1. From initial to 100px
  2. From previous (100px) to 300px // A new tween .to({ x: '300px', duration: 1 }, 2)
  3. From previous (300px) to 200px

 

But it plays it in the following order:

  1. From initial to 100px
  2. From previous (100px) to 300px
  3. From previous (100px) to 200px

 

It looks like the .to({ x: '200px' }) tween cached "previous" value after the first play as 100px. And inserting a new tween doesn't uncache it.

You can uncomment lines 6 and 16 and see that if it added right before .play() then it works as "intended".

 

So, the question is: is it an "ok" behaviour or is it a bug? How to workaround?

See the Pen dyeXXBr?editors=0010 by charnog (@charnog) on CodePen

Link to comment
Share on other sites

So... Invalidation resolved the issue.

timeline.pause(0).invalidate().play(0);

The question now: if I have a paused timeline (at arbitrary time) is it right method to .invalidate() to keep the things at their initial positions?

const totalTime = timeline.totalTime();
// .pause() at 0 to .invalidate() at "initial" values
timeline.pause(0).invalidate().pause(totalTime);
  • Like 2
  • Thanks 1
Link to comment
Share on other sites

Hey @Denis Gonchar welcome to the forum! 

 

Great that you've already found a solution! and posting it back here. I was looking in to the .revert() function in the understanding it would do what you want, but it doesn't (maybe @GreenSock or @Cassie could shine a light on that) 

 

Have you seen the following tutorial? The beginning part demonstrates what GSAP 'caches' when animating elements (and that made me think .revert() would solve)

 

 

.revert() not working or I'm mis understanding what .revert() does

 

See the Pen gOzMmYb?editors=0010 by mvaneijgen (@mvaneijgen) on CodePen

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

4 hours ago, mvaneijgen said:

.revert() not working or I'm mis understanding what .revert() does

From what I can tell, everything is working exactly as it should.

  1. revert() does NOT alter anything in the timeline itself. Think of it like rewinding to the very start and reverting all the properties that were affected by the animation.
  2. revert() doesn't flush everything out of the timeline nor does it force the tweens to invalidate() (flush the recorded starting/ending values). It is purely about reverting the values and it also removes it from the parent (sorta like killing it). In your case, however, you specifically called restart() so it shoved it back into its parent. That's unconventional. In general, you should consider a reverted animation to be dead and gone, not to be reused again. Obviously you CAN...but I just advise caution. I just find it easier for people to think in terms of it being gone. 
  3. I noticed you had repeatRefresh: true but that would do nothing because you didn't have a repeat value on that animation. 
  4. You misspelled immediateRender :)
  5. It's fine to do x: "100px" but you can shorten it to simply x: 100 since "px" is the default anyway. 
5 hours ago, Denis Gonchar said:

if I have a paused timeline (at arbitrary time) is it right method to .invalidate() to keep the things at their initial positions?

It depends what you mean by "initial positions". Are you just trying to rewind everything to the start? animation.progress(0) is fine. Or there are about 4 other ways you could do it :)

 

When a tween renders for the first time, it records the start/end values internally so that it can very quickly interpolate during the course of the tween. It sounds like maybe you want to FLUSH those values out, right? If so, .invalidate() is what you're looking for, yes. 

 

Is this what you wanted?: 

See the Pen rNvLGzB?editors=0010 by GreenSock (@GreenSock) on CodePen

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

Thank you all for the fast replies! That is Support!

 

On 9/11/2022 at 4:32 PM, mvaneijgen said:

Have you seen the following tutorial?

Nope, and thank you for this, now I know mooore.

 

On 9/11/2022 at 9:50 PM, GreenSock said:

Are you just trying to rewind everything to the start? It sounds like maybe you want to FLUSH those values out, right? If so, .invalidate() is what you're looking for, yes

Exactly. In my project I have a playhead that can be "paused" and then I can update tween beneath the playhead. For my use case I selected the following one:

const totalTime = timeline.totalTime();
timeline.totalTime(0, true).invalidate().totalTime(totalTime);

To keep the playhead where it was: I save the totalTime(), then rewind it to zero point, then invalidate the timeline and then fast-forward it back.

 

----

 

A follow up question: how to invalidate the duration of a played timeline with a tween?

 

Docs say:

Quote

Due to the fact that a timeline’s duration is dictated by its contents, using this method (.duration()) as a setter will simply cause the timeScale to be adjusted to fit the current contents into the specified duration, but the duration value itself will remain unchanged.

That's totally fine, but what if contents' duration was changed?

 

Steps to reproduce:

  1. Create a timeline
  2. Put a tween with 2 sec duration (which makes timeline duration eq 2 sec too)
  3. Play the timeline
  4. Change the duration of the tween to 4 sec
  5. Invalidate the timeline
  6. Play the timeline again
  7. The tween will be played only to 2 sec

 

Despite the duration of the tween was changed and timeline was invalidated the duration of the timeline will be still 2 sec.

See the Pen gOzwboP by charnog (@charnog) on CodePen

Why? And how to .invalidate()? How to workaround?

Link to comment
Share on other sites

Hi @Denis Gonchar

 

First, thanks for the super-clear AND reduced demo. Kind of rare that we get to work with demos like this :)

 

Your demo does a great job of proving that updating the duration of the tween doesn't seem to impact the duration of the timeline.

Whether or not that is a bug or just unexpected behavior I'll allow @GreenSock to address. 

 

For now, a valid workaround could be that you just re-add() the tween to the timeline AFTER you adjust it's duration.

 

timeline.add(tween, tween.startTime())

 

In the demo below the timeline starts with the tween having a duration of 2 and startTime() of 1.

This gives the timeline a duration of 3.

 

I then adjust the duration of the tween to 4 and re-add() it using it's stored startTime() of 1.

This gives the timeline the expected duration of 5 

 

See the Pen bGMwdyQ?editors=0011 by snorkltv (@snorkltv) on CodePen

 

Also note, this does not require an invalidate() call. 

 

While we're all here, I'm curious what the use case is where you need to adjust the duration of a tween after it's in a timeline. 

I'm guessing that maybe you are working on a sort of GUI where the user can build their own animations. If yes, you may just want to rebuild the entire timeline every time an adjustment is made to a tween. 

 

A very general approach would be to use an object to store all the starting values, durations, and start times of all your animations. Every time something needs updating just destroy the timeline and rebuild it. Just guessing this might be helpful. Interested to hear what you are up to.

 

  • Like 2
Link to comment
Share on other sites

Indeed, your minimal demos make it much easier to troubleshoot so thank you.

 

You did expose an edge case where the duration didn't get updated properly due to some internal optimizations that are in place to maximize performance by caching certain values. It should be resolved in the next release but in the meantime here's a fork of your demo that includes an "uncache()" helper function that'll force all the contents of a timeline to get uncached time-wise: 

See the Pen qBYaOaX?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Does that help? 

  • Like 3
Link to comment
Share on other sites

On 9/12/2022 at 10:35 PM, Carl said:

First, thanks for the super-clear AND reduced demo. Kind of rare that we get to work with demos like this :)

You're are welcome, guys! It's my own interest to enhance GSAP as I use it as 'engine' for the project. 🤘

 

On 9/12/2022 at 10:35 PM, Carl said:

While we're all here, I'm curious what the use case is where you need to adjust the duration of a tween after it's in a timeline. 

I'm guessing that maybe you are working on a sort of GUI where the user can build their own animations.

You're totally right, it's a GUI web-app that allows users to create sequences and compose things together. Big plans there...

 

On 9/12/2022 at 10:35 PM, Carl said:

If yes, you may just want to rebuild the entire timeline every time an adjustment is made to a tween. 

Yeap, the first version of an 'algorithm' was doing exactly this. Now imagine an animation that lasts for 3-5 minutes (I want to broaden our understanding what web-animations can be). To rebuild the timeline is 'ok' when there are few keyframes, but if there will be a lot of them then I want to do it in-place. Also, user can drag the keyframe along a timeline and number of events is huge (almost 60/sec; I'm going to throttle, anyways it will be around 20/sec).

 

On 9/12/2022 at 10:35 PM, Carl said:

For now, a valid workaround could be that you just re-add() the tween to the timeline AFTER you adjust it's duration.

It works, but partially, and with side effects. A new CodePen has arrived. 😀 Take a look at JS section, I left comments there.

See the Pen NWMRomL by charnog (@charnog) on CodePen

 

On 9/12/2022 at 10:45 PM, GreenSock said:

Indeed, your minimal demos make it much easier to troubleshoot so thank you.

You're welcome!

 

On 9/12/2022 at 10:45 PM, GreenSock said:

Does that help? 

Yes, it works, thank you! One issue is still there and I'm trying to validate on which side it is (probably mine). I will post here, when I get it.

  • Like 1
Link to comment
Share on other sites

Updated the last CodePen. Found out that it reproducible even without .re-add()-ing the tween.

@GreenSock can you take a look?

 

P.S. Probably related to the previous issue, but it's a new one (I mean that it has other steps to reproduce).

 

UPDATE (35 minutes later):

Maybe it can help: the last issue happens only when the playhead is over the tween. Watch a related video here.

 

Link to comment
Share on other sites

In your latest demo I may not have followed exactly but if you re-add() tween1 and tween2 after you mess with their duration or startTime() it seems to work. I log duration() of 4 twice

 

See the Pen PoebJEm?editors=0011 by snorkltv (@snorkltv) on CodePen

 

However, I don't want to get off track. You shouldn't have to do this as @GreenSock previously noted. 

 

I tried using the uncache() method but maybe I was using it wrong as I couldn't get it to help your latest demo.

 

Please stay tuned. Finding solutions for these types of issues can take some time to troubleshoot and test properly. 

 

16 hours ago, Denis Gonchar said:

I want to broaden our understanding what web-animations can be

 

Sounds cool. Sign me up!

  • Like 2
Link to comment
Share on other sites

31 minutes ago, Carl said:

I tried using the uncache() method but maybe I was using it wrong as I couldn't get it to help your latest demo.

Same thing in my project to. It works for me only if I change the .startTime() or .duration() of the last tween and re-play the timeline.

 

31 minutes ago, Carl said:

Please stay tuned. Finding solutions for these types of issues can take some time to troubleshoot and test properly. 

Yeap. I'm now using your workaround because it succinct and 'just works'. And trying to add as much issue-context as possible.

 

31 minutes ago, Carl said:

Sounds cool. Sign me up!

Already! 🤘

  • Like 1
Link to comment
Share on other sites

5 hours ago, Denis Gonchar said:

last CodePen. Found out that it reproducible even without .re-add()-ing the tween.

@GreenSock can you take a look?

I'm a little confused here - I already acknowledged the issue, said it would be resolved in the next release (it's already in the beta) and provided that uncache() helper function as a workaround but I don't see you using that in your demos. Am I missing something? 

 

See the Pen JjvbBzy?editors=0010 by GreenSock (@GreenSock) on CodePen

 

To be clear, the issue is related to changing the duration of an already-initted tween nested inside a timeline that has smoothChildTiming set to false would result in the parent timeline not adjusting to the new endTime of the child tween that was altered. Definitely an edge case and I'm glad you pointed it out. 👍

 

But let me know if there's something else you think I need to take a look at which isn't resolved by the helper function I already provided. 

  • Like 2
Link to comment
Share on other sites

30 minutes ago, GreenSock said:

Am I missing something? 

No. My bad, I didn't mean 'take a look at this again', all is fine, both workarounds are working. Thanks!

As I said before, I just tried to add more context to the issue when I found another steps to reproduce, I thought it would help.

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

Just to keep things together decided not to create another topic. So...

See the Pen GRdWror by charnog (@charnog) on CodePen

 

Steps:

1. Create two tweens

2. Place the playhead between them

3. Move the first tween after the playhead

4. Invalidate the timeline

5. The initial values* will be taken from the first tween

 

* The definition for the initial values: 'values that element has when there are no animations at all'.

 

You can watch a video representation here.

 

For example, if you had a div with x: 0 at 0 seconds after the steps it will have x: <value of first Tween> at 0 seconds.

 

Is that an issue or maybe I'm doing something wrong?

 

UPDATE (14 minutes later):

As a workaround I'm using the following:

1. Save const totalTime = .totalTime()

2. Set .totalTime(0).pause()

3. Do manipulations

4. Restore .totalTime(totalTime).play()

 

UPDATE (18 minutes later):

Sad, but it fixes only the case described above. If I 'restore the previous state' after the .invalidate() call it works like a charm.

Link to comment
Share on other sites

What's happening

GSAP is highly optimized for performance. One of those optimizations involves only having tweens render when their playhead changes. There are a bunch of checks internally to figure out when we can safely skip work. You've got two tweens that affect the same value and technically you've got them sitting directly on top of each other (one is a zero-duration tween that is at the same time as the one with a duration of 2 seconds). When you change the startTime so that it moves that tween PAST where the parent timeline's playhead is, that forces it to render at its starting state (because that's accurate). First you changed tween1, then tween2, thus tween2 rendered last. 

 

Then, when you rewind the playhead back to the start, those tweens don't render again because their playhead is ALREADY at their start. GSAP skips that. But in your case, since you changed the startTime() of tween2 last (and there's no more render), that's the state it stays in after you progress(0). 

 

Is this a "bug"? I suppose you could argue that, yes, but in order to "solve" it, we'd have to implement extra logic that would have to run on every single tick (expensive) and possibly render things when they don't really need it just to work around this one extreme edge case that I don't think I've ever seen in my entire 14+ year career of building/maintaining GSAP. So everybody would pay a performance tax for this "fix". That doesn't sound wise to me. 

 

Better solution

A simple technique that'd force everything to get rendered when rewinding is to jump to the end first: 

// push the playhead to the end and then back to the start
timeline.progress(1).progress(0);

By the way, there's absolutely no reason to pause() an animation if you're just going to play() it in the same tick. There's nothing "wrong" with it - it's just slightly wasteful. 

 

Summary

This is an extreme edge case that you can easily work around by pushing the playhead to the end/start, thus I don't think it's worth implementing extra logic in the core to work around because it'd impose a performance penalty on everyone (including the 99.9999% of users who would never run into the edge case to begin with). 

 

Does that help? 

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

  • 3 weeks later...

@GreenSock Jack, thank you for the definitive answer! It’s incredible to receive that level of support!

 

Sorry for the late response. I needed some time to play with it and validate it. To think about all the things.

 

Regardless of the “to bug or not to bug”

To answer the question, let me define the word. It is ‘something unexpected’. It was a bug at first glance, but if you say it is ‘intended’ then I will treat it as just ‘undocumented behavior’. And yes, given that it has the workaround, I agree it is "not so wise" to implement that kind of check on every tick. Just as a suggestion: maybe we need to prune the cache after the .startTime() was changed? Probably, it is more sophisticated than just 'prune the cache', but I tried to suggest it. 😎

 

Regardless “does that help?” question

Let me tell a story.

 

At the beginning of the story, I thought that it’s ‘ok’ to put two tweens on a timeline with identical .startTime() if the first tween is .set() tween (or .duration() === 0) and the second one is .to(). I took it as kinda hand-made .fromTo(). And it worked. I created a timeline, put some tweens on it, and got the expected behavior. Then I did some simple manipulations with their positions and durations, and after that, something was broken: random glitches of a target element ‘arrived’. So I decided to go deeper and find an answer to the ‘why?’ question. Take a look at the console of the pen.

See the Pen GRdmZja by charnog (@charnog) on CodePen

 

So, I realized that putting two tweens on the same position is not a good idea, and it is necessary to use .fromTo(). I didn’t what that because it create several cases when I need to transform .to() to .fromTo() or .fromTo() to .to(). Whatever it was, I implemented all the ‘edge’-cases and was happy until… I put .fromTo() tween at the beginning of the timeline. The same ‘issue’ with caching appeared. The trick with .progress(1).progress(0) wasn’t working. I said ‘ok’ and went deeper. I tried everything I could, even deleting the internal DOMElement’s cache, without success.

 

Currently, I realized that I over complicated everything and needed to revert the engine to the previous approach with .set() and .to(). Because in the case of .fromTo() there are much more edge cases when a user wants to use .set() (e.g. for display CSS prop). But what about that edge case with the internal linked list? For me, it is much easier to work around the 'issue' by putting the next .to() tween after .set() with the offset of 0.001 seconds so that the condition line will work as expected.

 

Summary

So... Does this help? Yes, definitely, and thanks again for that kind of reply. The current plan is to revamp it (my The Engine) via .set() .to() model and use .progress(1).progress(0) trick to handle the caching ‘issue’.

  • Like 1
Link to comment
Share on other sites

Thanks for the explanation. We actually did some significant work under the hood to accommodate the new revert(), gsap.context(), and gsap.matchMedia() and unfortunately a couple of bugs crept in related to .from() and .fromTo() tweens in version 3.11.0 through 3.11.2, but they've been resolved in 3.11.3. Sorry about any confusion/hassle there. So I wonder if when you were running into those "glitches" you mentioned with .fromTo(), perhaps it was due to one of those bugs (which, again, are resolved now). 

 

It sounds like you've already got a solution in place which is great, but you are welcome to try your other strategy again with 3.11.3 to see if those glitches are resolved. Or not. :)

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