Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
Eunomiac

How to Properly .reverse() a Repeating Hover Animation on "mouseleave"?

Go to solution Solved by OSUblake,

Recommended Posts

I have in my example a simple timeline with the following structure:

  .to(DOT, <expand to full size>, 0)
  .to(DOT, <repeatedly pulse>, ">")
  .to(TEXT, <spread out characters>, "<")

Normally, when I do hover animations like this, I simply call the .reverse() method on the "mouseleave" event.  However, when I do this with a timeline that includes a repeating component (the second .to() in my example), reversing the timeline reverse-animates through all previous loops of that repeating tween, leading to an excessively-long hover-off animation.

 

I thought I could just use an onReverse() callback and, within that, jump the playhead location to the end of the first repeat (so that, when it continues backwards to the start of the timeline, it only runs through one cycle of that repeating tween).  But, according to the docs, such a callback doesn't exist.

 

I then thought I could use the onRepeat() callback to reset the playhead to the start of the repeating animation each time it repeats, but then I figured I was too far into the weeds and that I should come here to discover a more elegant way to do this!

 

What is the proper way to pull this off?  Much thanks in advance for any help you can provide!

See the Pen poWBXam by eunomiac (@eunomiac) on CodePen

Link to comment
Share on other sites

  • Solution

Hi Eunomiac,

 

Reverse is going to play for however long the animation has been playing forwards. So what you can do is adjust the time of the animation to be on the first iteration doing a remainder calculation. I used 1.5 because the is the duration of the animation if it only had 1 repeat in it.

 

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

 

  • Like 1
Link to comment
Share on other sites

Excellent, and thank you --- it never occurred to me to refer to the timeline duration directly, which seems a bit obvious in hindsight!

Link to comment
Share on other sites

Clever solution, @OSUblake 

What is rawTime()?  I don't think I've come across that before.

 

Link to comment
Share on other sites

3 hours ago, Carl said:

What is rawTime()?  I don't think I've come across that before.

 

An animation's playhead isn't allowed to move beyond its start or end, of course. So for example, if your animation has a duration of 10, you'd never get a totalTime() of 11 because the playhead "stops" at the end. Think of rawTime() as the same thing except without those limitations, so it looks at wherever the playhead of the PARENT timeline is and figures out the corresponding time on the child even if it's beyond the start/end. 

 

So let's say you've got a 10-second animation that finishes and then 1 second later you check this: 

console.log(animation.totalTime()); // 10
console.log(animation.rawTime());   // 11

(that assumes that the parent playhead kept going, like it wasn't paused or reversed or whatever). 

 

Does that clear things up? 

 

It's not documented currently because I really wasn't sure it'd be useful to many people and I didn't want to clutter the API. If you all think it's worth officially documenting, I'm certainly open to that. 

  • Like 1
Link to comment
Share on other sites

3 hours ago, Carl said:

What is rawTime()?  I don't think I've come across that before.

 

I've come across it before just from looking at some of Jack's code, but didn't fully understand how it worked. So for this example, totalTime should work the same.

 

timeline.totalTime(timeline.totalTime() % 1.5).reverse();

 

Link to comment
Share on other sites

Thanks @GreenSock for the explanation. Thanks @OSUblake for chiming in too with the clarification!

after reading Jack's explanation I was having trouble figuring out why/if it was needed in your solution. 

 

As somewhat of an experienced user I like knowing about these "bonus" methods. On the other hand I can't say a bunch of obvious use cases are jumping out at me for when I would need it. So you can put me down for a vote for documenting it, but I won't be broken-hearted if it doesn't happen. 

Link to comment
Share on other sites

Usually the only time I tap into that is if I need to account for potential "time leak", like if the playhead may have stopped at the start/end and time elapsed since then.

For example, if I'm trying to do a seamless loop using an onComplete - let's say technically the animation was scheduled to stop at 1000ms and there's an update at 999ms and then the next update is at 1015ms so the onComplete fires but technically it was 15ms after it theoretically ended, I'd want to jump 15ms into the animation in the forward direction now instead of starting at 0. Make sense? It's exceedingly unlikely that anyone would notice that slight imprecision, but I obsess about things being accurate. Most other engines don't account for time drift like that which can add up over time and cause things to fall out of sync with other animations. 

 

Does that clear things up? 

  • Like 1
Link to comment
Share on other sites

15 hours ago, GreenSock said:

So let's say you've got a 10-second animation that finishes and then 1 second later you check this: 

console.log(animation.totalTime()); // 10
console.log(animation.rawTime());   // 11

Just to clarify one thing, which I think @OSUblake already touched on:  .rawTime() and .totalTime() should only be different if both of the following are true:

  • the timeline has a finite .totalDuration() (i.e. it includes no infinite repeats), and
  • the timeline's playhead is located beyond its .totalDuration()

Does this mean that a timeline's playhead continues to advance after the timeline has reached its conclusion and fired its .onComplete() callback, such that the .rawTime() of any timeline will continue to increment until the timeline is explicitly .pause()'d, .reverse()'d or otherwise altered?

 

Follow-up Question: Is it possible to derive that "1.5" value (i.e. the "duration with one repeat") from any of the Timeline properties/methods?  Or is it something you just have to manually code in?

Link to comment
Share on other sites

1 hour ago, Eunomiac said:

Does this mean that a timeline's playhead continues to advance after the timeline has reached its conclusion and fired its .onComplete() callback, such that the .rawTime() of any timeline will continue to increment until the timeline is explicitly .pause()'d, .reverse()'d or otherwise altered?

 

No. It will stop. The rawTime is based off of the timeline's parent. Every timeline has a parent, going all the way up to the globalTimeline, which is what drives everything. 

 

1 hour ago, Eunomiac said:

Follow-up Question: Is it possible to derive that "1.5" value (i.e. the "duration with one repeat") from any of the Timeline properties/methods?  Or is it something you just have to manually code in?

 

Not really, especially if you have a bunch of nested animations with repeats. I just set the repeat to 1 and logged outtimeline.duration().

 

Link to comment
Share on other sites

8 hours ago, Eunomiac said:

Just to clarify one thing, which I think @OSUblake already touched on:  .rawTime() and .totalTime() should only be different if both of the following are true:

  • the timeline has a finite .totalDuration() (i.e. it includes no infinite repeats), and
  • the timeline's playhead is located beyond its .totalDuration()

 

Not really. 

  • Even animations with repeat: -1 technically have a start, so if you reverse() it could reach the beginning. Think of repeat: -1 as just having a SUUUUPER long totalDuration. 
  • It's not just for the end of an animation - it could be the start too. Like if you're trying to do an infinite repeat in reverse, you might have an onReverseComplete that fires 15ms (or whatever) past the start in reverse in which case rawTime() could be useful. But honestly it's incredibly rare that anyone would need rawTime() in practical real-world scenarios. 
9 hours ago, Eunomiac said:

Is it possible to derive that "1.5" value (i.e. the "duration with one repeat") from any of the Timeline properties/methods? 

I'm a little fuzzy on what you're asking here - if you want to know how many seconds it would take for a timeline to do two iterations plus any repeatDelay, sure:

let cycle = tl.duration() * 2 + tl.repeatDelay();

But I think what Blake was saying is that it's not the timeline that has repeat: -1, it's one of the child animations, thus the timeline's duration() will be super long and the above formula won't work for that. So what you're basically asking (if I understand correctly) is "how can I calculate the duration of a timeline as if NONE of the children have any repeats?" Right? So yeah, that's much more tricky. I mean technically you could write a bunch of code to loop through all the children, alter the repeat() of each to be 0, and then check the timeline's duration() and then reset all the child repeats to what they were but that's a lot of effort. In my opinion, it'd be much cleaner to just use variables when you build out your animations such that you can easily calculate the iteration duration that you're after. 

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