Jump to content
Search Community

TweenEvent.COMPLETE fires before rendering complete

moxol test
Moderator Tag

Recommended Posts

I have noticed that TweenEvent.COMPLETE fires just before animation finishes.

In the provided example you can see that object stops tweening just before COMPLETE is fired.

When COMPLETE function event is finished, object reaches it's final tween position.

Sometimes COMPLETE fires 1 px before end and sometimes more.

I have added RENDER event to prove that actually object final position is reached after COMPLETE event.

 

Can that be solved?

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="[url="http://ns.adobe.com/mxml/2009"]http://ns.adobe.com/mxml/2009[/url]"
		   xmlns:s="library://ns.adobe.com/flex/spark"
		   xmlns:mx="library://ns.adobe.com/flex/mx"
  click="onClick()"
  >

<fx:Script>
 <![CDATA[
 import com.greensock.TimelineMax;
 import com.greensock.TweenMax;
 import com.greensock.data.TweenMaxVars;
 import com.greensock.events.TweenEvent;
 import flash.events.Event;

 private var timeline:TimelineMax = new TimelineMax();

 private function onClick():void
 {
  var tweenMaxVars:TweenMaxVars = new TweenMaxVars();
  tweenMaxVars.x(200, true);
  lbl.addEventListener(Event.RENDER, onRender);
  timeline.addEventListener(TweenEvent.COMPLETE, function():void
  {
for (var counter:Number = 0; counter < 1000; counter++)
{
 trace('test');
}

  });

  var tweenMax:TweenMax = new TweenMax(lbl, 0.2, tweenMaxVars);
  timeline.append(tweenMax);
 }

 private function onRender(event:*):void
 {
  trace(lbl.x);
  if (lbl.x == 200)
  {
lbl.removeEventListener(Event.RENDER, onRender);
  }
 }
 ]]>
</fx:Script>

<s:Label id="lbl" text="object" />
</s:Application>

Link to comment
Share on other sites

I think there's a little confusion happening here related to what it means for a tween to "complete" and how that relates to the graphics rendering pipeline in Flash/Flex. The tween/timeline will call its onComplete as soon as it finishes setting the values (via ActionScript). It will never fire earlier than that. You can verify this by doing a trace() of the values you're tweening in your onComplete - they'll be exactly what you expect. However, just because the values are set properly doesn't mean Flash/Flex has visually rendered the object yet on the stage/screen. That might take a few milliseconds more to accomplish. If you have a very heavy load on the graphics rendering pipeline (which, again, is completely unrelated to the tweening engine), it may take much longer than a few milliseconds.

 

It wouldn't be appropriate for the tweening engine to listen for a RENDER event before firing its onComplete because that would assume that all tweens are directly related to DisplayObjects (and that's not true) and it would also assume that no developers have code that needs to be notified of a tween's completion before executing other code that might alter DisplayObjects. Imagine if in your onComplete you changed the alpha of the target - if it runs after the RENDER event, that change wouldn't be seen until the next frame!

 

So in your case, if you need to wait for the render, just set up a listener from inside your onComplete that does exactly that - waits for the RENDER event to fire.

 

Make sense?

Link to comment
Share on other sites

I think there's a little confusion happening here related to what it means for a tween to "complete" and how that relates to the graphics rendering pipeline in Flash/Flex. The tween/timeline will call its onComplete as soon as it finishes setting the values (via ActionScript). It will never fire earlier than that. You can verify this by doing a trace() of the values you're tweening in your onComplete - they'll be exactly what you expect. However, just because the values are set properly doesn't mean Flash/Flex has visually rendered the object yet on the stage/screen. That might take a few milliseconds more to accomplish. If you have a very heavy load on the graphics rendering pipeline (which, again, is completely unrelated to the tweening engine), it may take much longer than a few milliseconds.

 

It wouldn't be appropriate for the tweening engine to listen for a RENDER event before firing its onComplete because that would assume that all tweens are directly related to DisplayObjects (and that's not true) and it would also assume that no developers have code that needs to be notified of a tween's completion before executing other code that might alter DisplayObjects. Imagine if in your onComplete you changed the alpha of the target - if it runs after the RENDER event, that change wouldn't be seen until the next frame!

 

So in your case, if you need to wait for the render, just set up a listener from inside your onComplete that does exactly that - waits for the RENDER event to fire.

 

Make sense?

 

I think tweening is first and most associated with graphics and animation, so it would make sense to make one more event like RENDERCOMPLETE that would fire after tween is finished on screen and not just on values because I think it's important and I think many developers would benefit.

 

This way I must add RENDER event listener inside COMPLETE event listener, and in RENDER I must add EXIT_FRAME listener because RENDER is just start of rendering and after EXIT_FRAME I can be sure that tween values are rendered on screen.

I have test it and it's the right way, but it's very ugly solution.

Link to comment
Share on other sites

That sort of thing is exactly what plugins are for. They're pretty simple to write. I whipped one together for you (see attached). Just drop it into the com/greensock/plugins directory and then do something like:

 

TweenPlugin.activate([OnCompleteRenderPlugin]);
TweenLite.to(mc, 2, {x:200, onCompleteRender:YOUR_FUNCTION});

 

Does that help?

OnCompleteRenderPlugin.zip

  • Like 1
Link to comment
Share on other sites

That sort of thing is exactly what plugins are for. They're pretty simple to write. I whipped one together for you (see attached). Just drop it into the com/greensock/plugins directory and then do something like:

 

TweenPlugin.activate([OnCompleteRenderPlugin]);
TweenLite.to(mc, 2, {x:200, onCompleteRender:YOUR_FUNCTION});

 

Does that help?

 

No, COMPLETE still fires before actual rendering is not complete.

 

Please look at this example and make plugin according to this:

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
		   xmlns:s="library://ns.adobe.com/flex/spark"
		   xmlns:mx="library://ns.adobe.com/flex/mx"
  click="onClick()"
  >

<fx:Script>
 <![CDATA[
 import com.greensock.TimelineMax;
 import com.greensock.TweenMax;
 import com.greensock.data.TweenMaxVars;
 import com.greensock.events.TweenEvent;
 import flash.events.Event;

 private var timeline:TimelineMax = new TimelineMax();

 private function onClick():void
 {

  var tweenMaxVars:TweenMaxVars = new TweenMaxVars();
  tweenMaxVars.x(200, true);

  var tweenMax:TweenMax = new TweenMax(lbl, 0.2, tweenMaxVars);
  timeline.addEventListener(TweenEvent.COMPLETE, function():void
  {
lbl.addEventListener(Event.RENDER, onRender);
  });

  timeline.append(tweenMax);
 }

 private function onRender(event:*):void
 {
  trace("RENDER: " + lbl.x);
  if (lbl.x == 200)
  {
lbl.addEventListener(Event.EXIT_FRAME, onExitFrame);
  }
 }

 private function onExitFrame(event:*):void
 {
  trace("EXIT FRAME: " + lbl.x);
  if (lbl.x == 200)
  {
test();
lbl.removeEventListener(Event.RENDER, onRender);
lbl.removeEventListener(Event.EXIT_FRAME, onExitFrame);
  }
 }

 private function test():void
 {
  for (var counter:Number = 0; counter < 1000; counter++)
{
 trace('test');
}
 }
 ]]>
</fx:Script>

<s:Label id="lbl" text="object" />
</s:Application>

 

Previous example uses event listeners on Timeline, and that works, but same solution doesn't always work on TweenMax, meaning that COMPLETE event on TweenMax sometimes fires after rendering is complete.

 

Here is example, and you will see that test() function sometimes never runs:

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
		   xmlns:s="library://ns.adobe.com/flex/spark"
		   xmlns:mx="library://ns.adobe.com/flex/mx"
  click="onClick()"
  >

<fx:Script>
 <![CDATA[
 import com.greensock.TimelineMax;
 import com.greensock.TweenMax;
 import com.greensock.data.TweenMaxVars;
 import com.greensock.events.TweenEvent;
 import flash.events.Event;

 private function onClick():void
 {

  var tweenMaxVars:TweenMaxVars = new TweenMaxVars();
  tweenMaxVars.x(200, true);

  var tweenMax:TweenMax = new TweenMax(lbl, 0.2, tweenMaxVars);
  tweenMax.addEventListener(TweenEvent.COMPLETE, function():void
  {
lbl.addEventListener(Event.RENDER, onRender);
  });

tweenMax.play();
 }

 private function onRender(event:*):void
 {
  trace("RENDER: " + lbl.x);
  if (lbl.x == 200)
  {
lbl.addEventListener(Event.EXIT_FRAME, onExitFrame);
  }
 }

 private function onExitFrame(event:*):void
 {
  trace("EXIT FRAME: " + lbl.x);
  if (lbl.x == 200)
  {
test();
lbl.removeEventListener(Event.RENDER, onRender);
lbl.removeEventListener(Event.EXIT_FRAME, onExitFrame);
  }
 }

 private function test():void
 {
  for (var counter:Number = 0; counter < 1000; counter++)
{
 trace('test');
}
 }
 ]]>
</fx:Script>

<s:Label id="lbl" text="object" />
</s:Application>

 

I am sure if you make plugin such that COMPLETERENDER event is fired like in first example, then it will work.

Link to comment
Share on other sites

I'm a little confused - the 2nd example you gave would only work the first time because you're doing a relative tween (tweenMaxVars.x(200, true)) and you've got conditional logic set up that'll only add the listeners if lbl.x == 200. Right? Or did I miss something?

 

Plugins are for tweens, so the OnCompleteRenderPlugin can't add an extra callback type to timeline instances themselves. You could, of course, just make sure your last tween in the timeline has the onCompleteRender. Otherwise, just do something like this:

 

var queue:Array = [];
var tl:TimelineMax = new TimelineMax({onComplete:queueRender, onCompleteParams:[YOUR_FUNCTION]});
//...add tweens...
function queueRender(func:Function):void {
queue.push(func);
this.addEventListener("exitFrame", renderHandler, false, 0, true);
}
function renderHandler(e:Event):void {
for (var i:int = 0; i < queue.length; i++) {
	queue[i]();
}
queue.length = 0;
this.removeEventListener("exitFrame", renderHandler);
}

That way, you can reuse those same functions anywhere - all you change is the onCompleteParams array so that it is populated with whatever function you want called after an "exitFrame" event happens. Totally reusable.

Link to comment
Share on other sites

This is going little bit confusing.

Let's keep it simple.

 

Let's start from this example:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
		   xmlns:s="library://ns.adobe.com/flex/spark"
		   xmlns:mx="library://ns.adobe.com/flex/mx"
  click="onClick()"
  >

<fx:Script>
 <![CDATA[
 import com.greensock.TimelineMax;
 import com.greensock.TweenMax;
 import com.greensock.data.TweenMaxVars;
 import com.greensock.events.TweenEvent;
 import flash.events.Event;
 import com.greensock.plugins.TweenPlugin;
 import com.greensock.plugins.OnCompleteRenderPlugin;

 private function onClick():void
 {
  TweenPlugin.activate([OnCompleteRenderPlugin]);

  TweenMax.to(lbl, 0.2, { x:200, onCompleteRender:test } );
 }

 private function test():void
 {
  for (var counter:Number = 0; counter < 1000; counter++)
{
 trace('test');
}
 }
 ]]>
</fx:Script>

<s:Label id="lbl" text="object" />
</s:Application>

 

onCompleteRender event is fired just 1,2 px before lbl reaches x = 200, in screen it stops and runs test() function and then finally on screen reaches 200 px.

I expected that first on screen reaches 200 px and then runs test() function.

 

P.S. Don't click more then once, just stick to first movement to 200 px, just for testing.

Link to comment
Share on other sites

Yes, apparently EXIT_FRAME doesn't necessarily get dispatched after things render, nor does the RENDER event in Flash. Baffling, I know :) And there are bugs in Flash that prevent the RENDER event from getting called at all in certain situations, so the most reliable way to know when things have completely rendered is to listen for the next ENTER_FRAME event. I updated the plugin to do exactly that now (available in the online downloads). Does that clear things up?

Link to comment
Share on other sites

Yes, apparently EXIT_FRAME doesn't necessarily get dispatched after things render, nor does the RENDER event in Flash. Baffling, I know :) And there are bugs in Flash that prevent the RENDER event from getting called at all in certain situations, so the most reliable way to know when things have completely rendered is to listen for the next ENTER_FRAME event. I updated the plugin to do exactly that now (available in the online downloads). Does that clear things up?

 

Where is download link, I can't find it?

Link to comment
Share on other sites

When I said "now available in the online downloads", I meant you can snag it in the same place as all the rest of the v12 stuff: http://www.greensock.com/v12/ (sorry if that wasn't clear)

 

This seems to be working fine now, I will test it of course bit more, but for now it looks ok.

Can I add event listener directly to TweenMax variable?

 

For example:

var tweenMax:TweenMax = new TweenMax(lbl, 0.2, { x:200 } );
tweenMax.addEventListener(TweenEvent.COMPLETERENDER, myFunction);

Link to comment
Share on other sites

No, you cannot add a listener like that currently, but I suppose it'd be possible to inject some code into the plugin to force the TweenMax instance to dispatch a "completeRender" event if that's necessary for you. Of course that'd only work if the tween is a TweenMax, not a TweenLite. Is that functionality you need?

Link to comment
Share on other sites

I just added an onCompleteRender() method to the TweenMaxVars and TweenLiteVars classes. Does that help?

 

Excellent!

Thanks, you are really attentive.

This kind of support I haven't seen anywhere, even on high cost commercial sites.

Every problem that I've encountered using Greensock has been solved.

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