Jump to content
Search Community

Animation reverse not completing after a few plays

oxley test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

I've been struggling with an issue for some time now and have exhausted all suggestions of a fix. I play and reverse an animation a few times and then suddenly the animation starts reversing to a certain point and stops. As I continue to run and reverse the animation, less and less of the full animation is reversed (presumably because the start point is shifting out). Tracking it in Chrome's "Inspect Element" I can see the CSS properties are left at a point indicating incomplete reversal. 


I have tried:

tl.set(objectToAnimate, {clearProps:"all"});

Also tried deleting the _gsTransform that attaches to the object being animated
delete objectToAnimate._gsTransform;


......but nothing works. Is this a bug?


Example at http://jsbin.com/irixuk/72/edit


To replicate bug click blue box, close and repeat until animation fails to return to start (after 3-5 plays usually).

Link to comment
Share on other sites

It's not a bug in GSAP - it's a bug in the logic flow in your code where you're creating a bunch of redundant click bindings that call ExpandContractBox() a bunch of times. If you add a console.log() or alert() in that function, you'll see that it gets called once the first time (and maybe the second one too), but then after that it starts exponentially increasing (two calls, then four, then 8, etc.). 


So you're actually creating a new TimelineLite instance each time through with redundant/conflicting tweens. So imagine the first one starting to play, but then the next one starts and overwrites that first one which had rendered, so the values aren't at their beginning values at that time (or at least not what you thought were the beginning). So that 2nd TimelineLite's to() tweens record the starting values at that point (and remember, they're now shifted because of that first timeline whose tweens just got overwritten). So when you reverse() that 2nd timeline, it correctly goes to its beginning values but those aren't what you expected - they were starting at an intermediate spot. See the issue? 


So GSAP is doing its job correctly - it's just all the redundant stuff and offset timing of conflicting tweens you're creating caused the reverse() to end at values you didn't expect. 


Okay, I'm afraid that explanation may not have made perfect sense so let me try one other example. Imagine element.x starts at 1 and you'll tween it to 10 in increments of 1 for the sake of simplicity. So when the tween begins, it says "what's element.x right now?" and it sees that it's 1 and it records that and begins the tween. It renders the first time, and sets element.x to 2, but then right at that point, you start ANOTHER tween that goes to 10 also. So that new tween does the same routine, asking "what is element.x right now?" and it records 2! So that tween goes from 2 to 10 instead of 1 to 10, and of course it overwrites the first tween. When you're moving forward, it may not look much different because you're ending at the same value (10), but when you reverse() that 2nd tween, it will correctly end at 2 instead of 1!


See why? 


So if you fix your click bindings and don't create redundancies, not only will you prevent a major performance issue, but your timelines will reverse() perfectly back to what you expected ;)

Link to comment
Share on other sites

Hi and welcome to the forums.
Sorry to hear you're having trouble but it has nothing to do with the engine, I believe the problem is on your code. The thing is that you have an event bubbling when you bind, then unbind and finally rebinding the click event to your elements, so the first time you click both functions are called just once; the same happens on the second click, but on the third the problems begin. When you click for the third time each function gets called two times, on the fourth click each function gets called four times, the next click eight times, then sixteen and so on.
That basically is telling the engine run eight times and the engine does so, the problem is that every time the engine starts the new timeline it records the starting values of the element, but eight times and the starting values grow every time the function executes. The element grows and when you click the close button it goes back to the starting value, but the last starting value, therefore the element gets a bigger size.
A possible solution could be to eliminate the bind and unbind and use JQuery's on and off. Another possibility would be to create a conditional logic for the click event, so when you click that variable will be true and when the timeline's reverse is completed it goes back to false again, something like this:

var tl = new TimelineMax({onReverseComplete: endAnimation}),
    isTweening = false;

        isTweening = true;
        //your code here

function endAnimation()
    //your code here
    isTweening = false;

Like that the element's click will trigger the code only when that condition is met and that'll be after the reverse is complete.


Hope this helps,



  • Like 1
Link to comment
Share on other sites

Thx Jack and Rodrigo for your suggestions.


I understand - thought it was something like that but couldn't pinpoint it.


I am pretty new to Javascript - whick console do you use to watch the execution of the code?


I have removed the click bindings and seem to have what I think is a pretty straight fwd example implementing Rodrigo's suggested approach above - but reverse animation not working at all now when the close button is clicked. It must be obvious - can you see where I am going wrong?


Have updated the example - http://jsbin.com/irixuk/88/edit




Link to comment
Share on other sites

Hi Dave,


Sorry for not posting earlier, you caught me with little free time.


The following code is working as I believe you want it to:


//we deifne the conditional logic as a global variable, every function can use it
var isTweening = false;

//Iterate through every element with a class "MoveResize" and create a timeline and variables for each of them 
$(".MoveResize").each(function(index, element)
    var objectToAnimate = $(this),
 	closeEl = $(objectToAnimate.find("#close")),
	settingcontent = $(objectToAnimate.find("#settingContent")),
	tl = new TimelineMax({onReverseComplete: endAnimation, onReverseCompleteParams:[$(this)], paused:true});
//We use the onReverseCompleteParams to indicate on which element the callback should be applied, more on this below.
	.to(objectToAnimate, 0.5, {zIndex: "9950"})
	.to(objectToAnimate, 1, {top: "5px", left: "5px", width: "510px", height: "360px", ease: Expo.easeOut}, "sync")
	.to(objectToAnimate, 1, {borderRadius: "8px"}, "sync")
	.to(settingcontent, 0.5, {opacity: "1"}, "content")
	.to(closeEl, 0.5, {opacity: "1", zIndex: "9999"}, "content")
	element.animation = tl;

//the click event defined for all the elements
	isTweening = true;
	changeCSS($(this));//change the css properties 
	this.animation.play();//play the timeline
	//define the close button for this instance
	var closeEl = $(this).find("#close"),
    	    resizeElement = this;//create this variable to use it on the close button click event
	    e.stopPropagation();//stop the click event from bubbling up the DOM
	    resizeElement.animation.reverse();//reverse the timeline

//the following function changes the css of specific elements on the click event
function changeCSS(element)
    element.find("#settingContent").css( "display", "inherit" );
    element.css( "cursor", "auto" );

//this is a global function that can be applied to any DOM element
//Here element is the onReverseCompleteParams defined when the timeline instance is created
function endAnimation(element)
    isTweening = false;
    element.css( "cursor", "pointer" );
    element.find("#close").css( "z-index", "1" );
    element.find("#settingContent").css( "display", "none" );


Okay even though the code is commented I'll go through some basic stuff.


First I defined the close button variable as closeEl, thus because there's a JS method called close (you can see it here), so you should try to avoid using methods defined and use words that aren't so generic.


In the code you had previous to your last post you were performing some actions on elements that weren't defined on the onReverseComplete callback. Those variables were defined inside the click function therefore they exist only inside that function and not for the callback. You can do some research for variables scope, I'll give you a couple of links later on this post.


Another thing is that you were creating a new timeline instance and a set of variables every time you clicked on an element. While GSAP takes the timeline and gets it ready for garbage collection, so when you click again your app won't get bloated with timelines instances, keeping things clean and nice, but you might work sometime with other libraries and/or plugins that aren't so neat, therefore is a good practice to use a loop to assign just once and for all the actions and variables for each element. For that JQuery's each is a great fit.


And finally you got the event bubbling issue, you'll see, the close button of your app was inside the element that takes the click event to trigger the animation, so you have to stop the click on the close button for going up to it's parents. For that JQuery's stopPropagation() does the trick, that's why at the beginning of the click function there's an e.stopPropagation, with that you're telling:"hey this click is for you and you only, don't throw your problems to your parents.... they have enough already!!"  :mrgreen:.


And finally there's the onReverseCompleteParams stuff. Those are a callback's best friend because it gives you the way to use your callback on any DOM element you select. Since the function is defined for an "element" we have to indicate what element is that and by stating that the parameter of the function is $(this) we're saying "apply the function to the element that was clicked to begin with".


I suggest you the following readings:


  • For a basic but very good Javascript and JQuery tutorials go to W3 Schools, they have the whole thing, JS, Jquery, Dom elements, etc. Is very basic but enough to get you started: http://www.w3schools.com/default.asp
  • A solid and up-to-date source is Mozilla Developer Network, you'll find tutorials and resources of a lot of things: https://developer.mozilla.org/en-US/
  • JQuery's official site has a learning center, I haven't looked too much into it, but it covers the basic stuff:http://learn.jquery.com/

Javascript's variables and variables scope:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Values,_variables,_and_literals#Variable_scope


More on event bubbling or event propagation: http://javascript.info/tutorial/bubbling-and-capturing


And last but not least, your app looks very well keep up the good job!!


Well I hope this helps you



  • 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.
  • Create New...