Jump to content


Instant animations in scrubbed timelines get stuck

Moderator Tag

Recommended Posts

I ran into some trouble today using .set in a scrubbed timeline. Everything works on the first scroll, but when I scroll back to the top the instant tweens stay stuck at the end. I can work around this by using a really small duration (like 0.001) on a regular .to call, but I wouldn't expect there to be a big behavior difference between a duration of 0 and 0.001. Is this the intended behavior or is it a bug?

See the Pen VwEqVxL by RJWadley (@RJWadley) on CodePen

Link to comment
Share on other sites

This looks like expected behaviour to me.

As soon as the timeline starts, the set and 0 duration tweens are moved to xPercent: 100, and they stay at xPercent: 100 for the entire timeline. So no matter where the playhead of the timeline is, they are always atxPercent: 100.

There's no point on the timeline where the set tween, or 0 duration tween exist at xPercent: 0

They're at xPercent:0 before the timeline starts playing, but you can't reverse() back to that point. You can only reverse back to progress 0, and at progress 0, they're already at xPercent:100

When a tween has a duration, however small that number is, even if it's 0.00000001, there's a small point on the timeline where that element existed at xPercent: 0 so you can reverse() back to that point.


0                                                                                                                       1

|-------------------------------- tl progress ---------------------------------|

[set:  xPercent 100 ] ------------------------------- [set: xPercent 100 ]


[to (0.0001) :  xPercent 0 ] ------------- [to (0.0001) : xPercent 100 ]

Hope this helps!

  • Like 3
Link to comment
Share on other sites

Ah, that does help me understand it. A bit unintuitive, but I'm sure it's probably like that for a reason. It's definitely a bit of an edge case, since it's only instant tweens.




I definitely think it would be worth putting some thought into what the behavior should be in this case. I think changing changing the duration should only impact timing, not behavior. It's also a recurring cause of animation bugs on my team, even those of us who've been working with GSAP for years. Then again, I'm nowhere near as experienced in animation as any of you guys are, so if you decide it genuinely should be this way then I'll trust that.


Here's another fun example I made while trying to figure out the behavior. It does a better job of showcasing the different behaviors in each of the possible cases. 

See the Pen BaqbyEV?editors=0010 by RJWadley (@RJWadley) on CodePen




  • Like 1
Link to comment
Share on other sites


Ah, that does help me understand it. A bit unintuitive, but I'm sure it's probably like that for a reason.

Yeah, I'd have to disagree on this. If I add a set tween to the beginning of a timeline I expect the timeline to start at that set state. It's an explicit instruction to immediately set a value. It's not saying 'animate to'


Similarly with your demo you've linked above, you've said 'doesn't initially apply' to all the from tweens. But quite the opposite is true, they are all immediately 'applying' by rendering at their end value.


Let's break it down a bit - first step, initial positioning


I think potentially the thing that's tripping you up here is referring and thinking of 0 duration tweens as 'animations'


The animation is a simple move from left to right.


The thing is, they aren't animations. Animations imply a duration, in order to animate we have to have some values to animate between and a period of time to 'tween' between those values. What you have here with 0 duration tweens and set 'tweens' are immediate commands.


Let's just take a look at the initial positioning. I've added some set tweens, some time stamps so that the starts are staggered and I've got rid of scrollTrigger for now (as that adds some different rendering behaviour which will likely confuse things further)

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


First thing to know is that all from() tweens render immediately by default. Now, an immediately rendered from tween with a zero-duration will render immediately at the end of the tween, the final state. Because it's a from tween it runs backwards, so you're 'setting' the end value. You'll never see the values in the vars object because those are the initial values.

The two tweens and set calls would also render immediately if they weren't on a timeline as they have zero duration. I've added some standalone tweens so you can see this. BUT, because they're on a timeline they're set to immediateRender: false and they're waiting for the playhead to hit them in order to 'set' their position.




If you bop immediateRender:true on all the tweens (commented out for you in the defaults) you'll see that all the tweens act the same as the from tweens, not waiting for their place on the timeline, just immediately rendering at their end state.

Probably worth saying too that 0 duration from tweens are incredibly uncommon and unintuitive. From tweens essentially say 'animate from these values' which is useful. But a zero duration from tween is basically a back to front, upside down set call. 🫠 It's inherently going to ignore everything in the vars object. It makes my head feel funny just trying to read it. So I really don't know why anyone would be using a zero duration from tween in the first place.



It seems to me that what you're running into and trying to understand is basically immediateRender - So here's some information. it's a little tricky to wrap your head around but it should help things click into place a little.



And here's a deeper dive into this behaviour by Jack

If that makes sense and we're on the same page I'm happy to elaborate more about the reversing behaviour and how ScrollTriggered timelines differ from normal timeline rendering.

Also, you're not the first to get baffled by this and you won't be the last!

Hope this helps!

  • Like 5
Link to comment
Share on other sites

Thanks! Love the deep dive you attached. Makes a lot more sense, and I think we're on the same page now.


Here's a real zero duration from tween I've yoinked from a scrubbed timeline in one of our codebases.

// raw code from the file
timeline.from(shutter, {
  opacity: 0,
  pointerEvents: "none",
  duration: 0,
}, 1.5)

// a slightly clearer way to write this
timeline.fromTo(shutter, {
  autoAlpha: 0,
}, {
  autoAlpha: 1,
  duration: 0,
}, 1.5)

The intent here is for shutter to be hidden until the specified point in the timeline, at which point it should instantly appear.

Put more imperatively:

- initially, apply the start state.

- when passed in the forwards direction, apply the end state.

- when passed it the backwards direction, apply the start state.


Is the best way to handle situations like this just to use an infinitely small duration, or is there a better way? Also, if you can think of any, is there anything that an infinitely small duration can't do that a zero duration can (specifically in the context of scrubbed/reversed timelines)?



Link to comment
Share on other sites

Yeah, a ton of thought has been put into zero-duration tweens (as Cassie said) and they're just inherently tricky because there's literally no distinction between the start and end positions. If the playhead lands directly on top of that spot...is it supposed to render the start or end state? To complicate things further, .from() tweens that immediately render bring a whole different set of challenges - in the vast majority of cases people want from() tweens to render immediately which means the starting state shouldn't get baked into the tween (if it did, anytime the playhead is at a position BEFORE the from() tween, it'd render at that pre-tween state which is almost surely different than the "from" state). 


Anyway, I'll resist writing 3 pages about all the challenges, why things behave the way they do, and why changing the behavior could cause a lot of other problems...but suffice it to say that you should be able to just use a duration of 0.001 to accomplish your goals (if I understand them correctly). 

  • Like 4
Link to comment
Share on other sites

So for initial state setting I usually either just use CSS or I use set the initial state outside of a timeline. Then the shutter would be a set tween on a timeline.

My animation files usually end up looking like this - set initial state and transform origins up top, then you have a fresh starting point for the timelines.


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

Although nothing wrong with a small duration tween here either, different strokes for different folks and all.

  • Like 4
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.