Can I have two separate Timelines and animate same object?

Question: can I add to Timeline UX tags, and animate the same object?

So, Timeline1 animates the Target, lets say moves it.

At some point later in time, Timeline2 animates the same Target, and move it somewhere else.

Is that doable? Please refer to:
https://www.fusetools.com/community/forums/permalink/cebc0ac8-ed64-45c3-aab4-6f13a2f7fc39

Where its said:
“In other words: they animate to the end of their duration and then hold that value. This means that multiple animators on the same target will give unexpected results.”

But the comment was for the same target in one Timeline. How about two?

I can not get it to work with two timelines, it seems…

Yes, many triggers can animate the same object/property simultaneously. Fuse has an animation mixer system that resolves this and makes sure it can always return to the rest state.

The question here is; what do you expect to happen when you use two timelines? You expect the animations to add, average, one of them to dominate? This can all be configured in UX using the MixOp property on the animators.

https://www.fusetools.com/docs/fuse/animations/animator/mixop

Well, in this case I expect the timeline2 to continue from where it “left off” after timeline1.

Basically, timeline1 moves an icon in and animates some text. After a certain event occurs, i set an Observable to true, which will trigger the timeline2 (WhileTrue in UX) to kick on the second animation, which should move the icon out of frame.

The second timeline doesnt seem to kick off, it never runs, the icon isnt move out of frame.

<StackPanel ux:Name="sp1" X="50%" Y="25%" Anchor="50%,50%" Width="400">
    	<AlfaPro ux:Name="alfaProIcon"  />
    	<TextView ux:Name="tvWelcome" Value="Welcome to Crime Reporter! " Alignment="Center" Opacity="0" TextColor="#fff" />
	</StackPanel>

	
	<WhileTrue Value="{doExitAnim}">
		<Change timeline2.TargetProgress="1"  />
	</WhileTrue>

	<WhileTrue Value="{doEntryAnim}">
		<Change timeline.TargetProgress="1"  />
	</WhileTrue>

 	<Timeline ux:Name="timeline" Progress="{jsProgress}" >
 		<Move Target="sp1" Y="-400" Duration="0"  />
 		<Change Target="tvWelcome.Opacity" Delay="0.5">
 			<Keyframe Value="1" Time="0.5" />
 			<Keyframe Value="1" TimeDelta="0.5" />
 			<Keyframe Value="0" TimeDelta="0.5" />
 		</Change>
    	<Move Target="sp1" Y="400" Duration="0.5" Delay="0.1" Easing="BounceOut" />
    </Timeline>

    <Timeline ux:Name="timeline2" >
    	<Move Target="sp1" Y="-400" Duration="0.5" Delay="0" />
    </Timeline>

Hi, try putting a <Callback Handler="{foo}" /> in the second timeline to check if it is triggered at all

Ok, I tried that like this:

<Timeline ux:Name="timeline2" >
    	<Move Target="sp1" Y="-400" Duration="0.5" Delay="0" />
    	<Callback Handler="{t2callback}" />
    </Timeline>

and then in JS:

        function t2callback()
        {
        	console.log("t2callback");
        }

        doEntryAnim = true;

		module.exports = {
		    doNavigate : doNavigate,
		    jsProgress : jsProgress,
		    isAnimDone : isAnimDone,
		    doEntryAnim : doEntryAnim,
		    doExitAnim : doExitAnim,
		    t2callback : t2callback
		};

And I never see the console log message =(

Im reposting the entire page here, for reference:

<Page ux:Class="SplashScreen">
	<Router ux:Dependency="router" />

	<JavaScript>
		var FH = require('../js/filehandler');
		FH.initPref();
		FH.clearPrefs()
        
        var Observable = require("FuseJS/Observable");
        var jsProgress = Observable(0);
        var doEntryAnim = Observable(false);
        var doExitAnim = Observable(false);

        var isAnimDone = jsProgress.map(function(value) {
        	console.log("1: " + jsProgress);
        	if (jsProgress.value == 1)
        	{
        		console.log("2: done!");
        		doNavigate();
        	}

        	return jsProgress == 1;
    	});

    	function doNavigate()
        {
        	console.log("3: doNav");

        	doExitAnim = true;
        }

        function t2callback()
        {
        	console.log("t2callback");
        }

        doEntryAnim = true;

		module.exports = {
		    doNavigate : doNavigate,
		    jsProgress : jsProgress,
		    isAnimDone : isAnimDone,
		    doEntryAnim : doEntryAnim,
		    doExitAnim : doExitAnim,
		    t2callback : t2callback
		};

	</JavaScript>

	<StackPanel ux:Name="sp1" X="50%" Y="25%" Anchor="50%,50%" Width="400">
    	<AlfaPro ux:Name="alfaProIcon"  />
    	<TextView ux:Name="tvWelcome" Value="Welcome to Crime Reporter! " Alignment="Center" Opacity="0" TextColor="#fff" />
	</StackPanel>

	
	<WhileTrue Value="{doExitAnim}">
		<Change timeline2.TargetProgress="1"  />
	</WhileTrue>

	<WhileTrue Value="{doEntryAnim}">
		<Change timeline.TargetProgress="1"  />
	</WhileTrue>

 	<Timeline ux:Name="timeline" Progress="{jsProgress}" >
 		<Move Target="sp1" Y="-400" Duration="0"  />
 		<Change Target="tvWelcome.Opacity" Delay="0.5">
 			<Keyframe Value="1" Time="0.5" />
 			<Keyframe Value="1" TimeDelta="0.5" />
 			<Keyframe Value="0" TimeDelta="0.5" />
 		</Change>
    	<Move Target="sp1" Y="400" Duration="0.5" Delay="0.1" Easing="BounceOut" />
    </Timeline>

    <Timeline ux:Name="timeline2" >
    	<Move Target="sp1" Y="-400" Duration="0.5" Delay="0" />
    	<Callback Handler="{t2callback}" />
    </Timeline>
  
</Page>

One error I can spot by glancing at the code is this:

 doEntryAnim = true;

That overwrites the entire observable, which is not what you want. What you want to do is:

doEntryAnim.value = true;

(You have the same error in at least one more place)

However, I think you need to take one step back here and start with describing what you want to achieve (your use case), and then we can advice on the best way of achieving that. Your code above is very un-Fuse-like.

Timeline is not meant to be used much at all, as you can see you end up micro-managing animations from JS, which you are not meant to be doing at all in Fuse. Instead, you should focus on using semantic triggers like WhileTrue, WhileSelected etc. to express animations.

If you describe what you are trying to achieve from a high level, I’m happy to help discuss how to achieve it, instead of debugging code that is full of antipatterns.

Right, I missed the .value. Strange thing is - it works for the start animation?

doEntryAnim = true;

I do that, and the animation starts?

Strange thing is - it works for the start animation?

It’s not strange at all, because true is the initial value that you export from the module.

So, here I am doing this:

  1. Animate in a short “welcome”; move an icon, animate a welcome text
  2. Meanwhile, Im an checking some settings and stuff
  3. When the intro-anim is done, I want to navigate to another page, depending on step 2 above, and when I decided to navigate somewhere, I want another animation to occur, removing the icon and switching page

I want the intro and exit anims to be logically separated (not in one Timeline/trigger), so that I can control it from code, when it starts etc.

And I am sure that its very un-fuse-like code, but its hard to understand how its supposed to work or look, I feel.

Anders Lassen wrote:

Strange thing is - it works for the start animation?

It’s not strange at all, because true is the initial value that you export from the module.

Right, ok. Thanks, now I get the second timeline to be triggered =)

Keep these three things separate.

For the welcome animation, you can use a WhileActive to create an animation that plays automatically when the page is active, Or, you can use a Timeline if you want to control the animation from JS. Timelines can be played from JS by giving them a name:

 <Timeline ux:Name="t1">

And then in JS

 t1.playTo(1);

The “Meanwhile, Im an checking some settings and stuff” is irrelevant for the animation. JS and UX are on separate threads. If you want an animation to play while your JS is busy, simply make an observable of whether your app is busy

 var isBusy = Observable(true);

And then you can create an eternal animation that plays while you are busy, for example

<WhileTrue Value="{isBusy}">
    <Spin Frequency="3" />
</WhileTrue>

Removing the icon etc when animating away can be done by t1.playTo(0) or automatically with WhileActive when the page deactivates.

So, Im trying to get a grip on Fuse, but I keep running into problems that I cannot explain.

I am pasting the code below again.

As you can see, the “timeline1progress” is an Observable, that is in UX connected to the Progress in timeline1. It logs the progress to console, and it works as expected; it prints “t1: …” and so on until Progress == 1.

However, the “timeline2progress”, which is built exactly the same way, does not work as expected. When timeline2 is animating, I see no logs to console.

How, the reasons seems to be that the variable “test” isnt in the module.exports, but initially I never bothered with the “var test” at all, as I am not interested in the return value anyways; i just want the code inside the function to be executed. But if I add the “test” variable to module.exports, it works as expected.

Why? The timeline2progress is already in the module.exports, so why do I need the “test”?

<Page ux:Class="SplashScreen">
	<Router ux:Dependency="router" />

	<JavaScript>
		var FH = require('../js/filehandler');
		FH.initPref();
		FH.clearPrefs()
        
        var Observable = require("FuseJS/Observable");
        var timeline1progress = Observable(0);
        var timeline2progress = Observable(0);

        var doEntryAnim = Observable(false);
        var doExitAnim = Observable(false);

        var isAnimDone = timeline1progress.map(function(value) {
        	console.log("t1: " + timeline1progress);
        	if (timeline1progress.value == 1)
        	{
        		console.log("t1: done!");
        		doNavigate();
        	}

        	return timeline1progress == 1;
    	});

    	var test = timeline2progress.map(function(value) {
        	console.log("t2: " + timeline2progress);
        	if (timeline2progress.value == 1)
        	{
        		console.log("t2: done!");
        		doNavigate();
        	}
        	
        	return timeline2progress == 1;
    	});

    	function doNavigate()
        {
        	console.log("3: doNav");
        	doExitAnim.value = true;
        }

        doEntryAnim.value = true;

		module.exports = {
		    doNavigate : doNavigate,
		    timeline1progress : timeline1progress,
		    timeline2progress : timeline2progress,
		    isAnimDone : isAnimDone,
		    doEntryAnim : doEntryAnim,
		    doExitAnim : doExitAnim,
		};

	</JavaScript>

	<StackPanel ux:Name="sp1" X="50%" Y="25%" Anchor="50%,50%" Width="400">
    	<AlfaPro ux:Name="alfaProIcon"  />
    	<TextView ux:Name="tvWelcome" Value="Welcome to Crime Reporter! " Alignment="Center" Opacity="0" TextColor="#fff" />
	</StackPanel>

	
	<WhileTrue Value="{doExitAnim}">
		<Change timeline2.TargetProgress="1"  />
	</WhileTrue>

	<WhileTrue Value="{doEntryAnim}">
		<Change timeline.TargetProgress="1"  />
	</WhileTrue>

 	<Timeline ux:Name="timeline" Progress="{timeline1progress}" >
 		<Move Target="sp1" Y="-400" Duration="0"  />
 		<Change Target="tvWelcome.Opacity" Delay="0.5">
 			<Keyframe Value="1" Time="0.5" />
 			<Keyframe Value="1" TimeDelta="0.5" />
 			<Keyframe Value="0" TimeDelta="0.5" />
 		</Change>
    	<Move Target="sp1" Y="400" Duration="0.5" Delay="0.1" Easing="BounceOut" />
    </Timeline>

    <Timeline ux:Name="timeline2" Progress="{timeline2progress}" >
    	<Move Target="sp1" Y="-400" Duration="4.5" Delay="0" Easing="BounceOut" />
    </Timeline>
  
</Page>

It is a common design practice in Rx-like frameworks that obserables do not pipe values through unless you are subscribing to them. Otherwise any map() would mean a permanent memory leak - there would be no way to know when you are no longer interested in the values coming out.

Ways to subscribe to an observable is for example:

  • Calling .subscribe(module) creates a subscription that is tied to the lifetime of the module
  • Adding it to the module.export does the same thing
  • Adding an .onValueChanged(module, function() { ... }) haandler does the same thing

In general, controlling the progress of a timeline with an observable is extremely poor practice and will lead to lags and unexpected jittering, as JS and UI runs on separate threads (for performanc reasons). You should control the timeline by calling methods like .playTo() on the timeline, not data-bind the progress.

Right, someone told me in some thread to data-bind the Progress property so I could determine when the animation was done.

The playTo method isnt really what I am after in this case; i want to detect when animation is done, and then, if the app isnt “busy”, then do another animation and change page…

To get a callback when an animation is done (or at any point on the timeline), put a

  <Callback Handler="{callback}" Delay="desired point in time" />`

But if I change the Timeline, Id like to avoid changing the callback. ISnt there a Callback where I can set Progress=“1” instead of Delay?

Currently, no, you have to update the Delay, but that’s a good feature suggestion (for another thread)

Apparently, it already exists?

https://www.fusetools.com/community/forums/permalink/ee026ddf-62b3-4457-9c6c-c44a75a2e1e6

Ah yeah :slight_smile: I was looking at the wrong docs. Nice catch