greensock / GSAP

GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
https://gsap.com
19.85k stars 1.72k forks source link

onComplete for TweenMax is not fired 100% if inside TimelineMax #162

Closed kambing86 closed 8 years ago

kambing86 commented 8 years ago

I have created 100 TimelineMax and each of them has 2 TweenMax, and the onComplete of the TweenMax is not fired 100%, so I have to move the onComplete function to TimelineMax to avoid the issue

jackdoyle commented 8 years ago

We cannot seem to reproduce this - can you show us a reduced test case, maybe on codepen or jsfiddle? That'd be SUPER helpful.

Also, you are using the latest version of GSAP, right?

kambing86 commented 8 years ago

http://codepen.io/anon/pen/PzQEAk

and the fixed and expected result (onComplete function at TimelineMax) is at http://codepen.io/anon/pen/JKprqw

Please check the code below

const animationTime = 0.1;
const spinLetters = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,':()&!?+-";
const random = Math.random;
const ceil = Math.ceil;
const floor = Math.floor;
const span = "<span>";

function Letter() {
  var that = this,
    useChar = "",
    savedChar = "",
    index = 0,
    count = 0;
  var letter = $(span).addClass("letter");
  var topText = $(span).addClass("flap top").append($(span).addClass("text")).appendTo(letter).find(".text");
  var bottomText = $(span).addClass("flap bottom").append($(span).addClass("text")).appendTo(letter).find(".text");
  var flap = $(span).addClass("flap falling").append($(span).addClass("text"));
  var flapText = flap.find(".text").text(" ");
  $(span).addClass("fold").append(flap).appendTo(letter);

  var timeLine = new TimelineMax({
    paused: true
  });
  timeLine.add(TweenMax.to(flapText, animationTime, {
    scaleY: 0,
    ease: Linear.easeNone
  }));
  timeLine.add(TweenMax.to(flapText, animationTime, {
    scaleY: 1,
    ease: Linear.easeNone,
    onStart: function() {
      flap.css({
        top: 0,
        bottom: "auto"
      });
      flapText.text(useChar).css({
        top: "-.68em"
      });
    },
    onComplete: function() {
      flap.css({
        top: "",
        bottom: ""
      }).hide();
      flapText.css({
        top: ""
      });
      bottomText.text(useChar);
      if (count > 0)
        spin();
    }
  }));

  that.rn_letter_rn = letter;
  that.rn_spin_rn = function(character) {
    savedChar = character;
    index = floor(random() * spinLetters.length);
    count = 5 + ceil(random() * 10);
    setTimeout(spin, ceil(random() * 500));
  };

  function spin() {
    count--;
    if (count <= 0) {
      useChar = savedChar;
    } else {
      index++;
      index = index % spinLetters.length;
      useChar = spinLetters[index];
    }
    topText.text(useChar);
    flap.show();
    timeLine.play(0);
  }
}

if I move the onComplete to TimelineMax level, then it's fine. so I suspect that the cause is, onComplete function calls the timeline to play from position 0 again, and calling that from child level might be causing the issue.

note that this is happening only when there are many instances created and causing lags

jackdoyle commented 8 years ago

Yikes! A few comments:

  1. Please provide a reduced test case that's readable and relatively concise (your demos used minified JS code and several other libraries)
  2. I'd recommend avoiding setTimeout(). The way you're using it can cause performance problems. It's much better/faster to use a TweenLite.delayedCall() instead, as that uses the same requestAnimationFrame loop that the core GSAP engine uses, ensuring that everything is perfectly synchronized.
  3. Did you intend to use jQuery for some of the animation, like show() and hide()? That's technically fine, but it's definitely quite a bit slower than GSAP.

If I understand your description properly, what you're experiencing isn't a bug at all - it's expected behavior. If, for example, you have a bunch of child tweens on a timeline that end at a certain spot, and one of them moves the playhead of the parent timeline from inside its onComplete, the rest of those tweens wouldn't fire their onCompletes because you just moved the playhead meaning they're no longer at their end.

Remember, the only way a tween plays is by having its parent timeline's playhead sweep across it. Think of it kind of like a record player and a needle.

Did I misunderstand something?

kambing86 commented 8 years ago

ok, thanks for the comments.

but please check my code in the comment, where every TimelineMax has 2 tweens, and only have one onComplete on the last tween, it's a simple setup where the last tween checks the condition whether to replay the timeline.

so by running the onComplete on tween level, resetting the playhead back to position 0 might not calling the onComplete on timeline level (that's understandable and is fine), but should not skip the onComplete on tween level after playing the timeline for second time.

jackdoyle commented 8 years ago

Are you saying that the entire onComplete doesn't get called sometimes or that your spin() function doesn't get called even when count > 0?

It'd really help if you created a reduced test case in codepen that could easily be edited and played with to see exactly what's going on in your code and how changes affect things. I suspect it might be a logic issue in your code, but I definitely want to squash any bugs if they exist.

I'm also very curious what'd happen if you swapped in TweenLite.delayedCall() for setTimeout().

Oh, and was there a particular reason you used all minified code in the codepen JS panel?