Jump to content
GreenSock

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

Binding to timeline

Recommended Posts

I've created a "Scrubber" component that's meant to control a TimelineLite instance. The timeline is passed to other components so that they can add tweens, timelines, etc. However, these may not be added synchronously. The problem, then, is that I have no way of updating my Scrubber when the duration of the timeline changes.

 

On a related note, timelines only one of each callback type (onUpdate, etc.).

 

Given these two issues (duration not observable, no "safe" event listener adding), is it not possible to bind a control to a timeline? Or is there just some trick that I'm not seeing?

 

Any info about what people are doing to accomplish this, or links to anything the author's said about binding like this is much appreciated! Thanks!

Link to comment
Share on other sites

Hello matthewwithanm, and Welcome to the GreenSock Forum!

 

I'm little confused on what exactly the behavior you want or are seeing without viewing your code in context.

  • are you trying to bind an event listener to an element in the timeline?
  • or/and Are talking about trying to see the duration output within an onUpdate() callback?

Is it possible so we can better help you.. if you can provide a reduced codepen example so we can see your code in a live editable environment?

 

Here is a nice video tut by GreenSock on How to create a codepen example demo.

 

This way by seeing your code in action, and code that we can test and edit, we can better assist you!

 

Thank You :)

Link to comment
Share on other sites

Hi Jonathan. Apologies for not being clear.

 

I'm simply trying to create a control that's bound to a timeline instance. When you manipulate the control, it affects the timeline; when the timeline is played, it affects the control.

 

See the Pen EKHAi by anon (@anon) on CodePen

is a much simplified pen demonstrating the kind of thing I'm doing.

 

You'll notice that the <input> currently doesn't have the correct max, since it's not updated after the timeline's duration is changed. If you uncomment line 22, you can see how it's meant to work.

 

My issue is that I can't just call my code's equivalent of updateScrubber() after something's added to the timeline because that happens far away from where the timeline is created and (like in the pen) asynchronously.

 

The normal way this kind of binding would be accomplished is listening for duration changes on the timeline and calling updateScrubber() whenever they happen, however TimelineLite doesn't dispatch any events or have any callbacks for that.

 

The second part of my question is about the safety of adding event callbacks in general. You can imagine that I might like to pass my timeline around and have other things bind themselves to it (like my scrubber is). However, because there can be only one callback for each event type, this can't be done safely (you might clobber a previous binding).

 

I know I'm not the first to try to bind something to a timeline so I'm hoping for some tips about what people have done or some information about how the author intended for this to be done.

 

Thanks!

Link to comment
Share on other sites

Hi,

 

The issue is that the the scrubber is not being updated. The timeline reports changes in duration when new instances are added but they're not reflected in the scrubber position until the mouse down event on the scrubber. Honestly I've never used the native UI before. For some time I used jQuery's UI slider until Draggable was released. Since then creating a custom slider became much easier. Something like this:

 

See the Pen Batoc by rhernando (@rhernando) on CodePen

 

Also a couple of days ago this post came along with a great solution:

 

http://forums.greensock.com/topic/9680-lightweight-slider-for-gsap-timeline/

 

Finally for binding several functions on a callback, yep that's a tricky one and can't think about a way to do that instead of create a function with a lot of functions called in it, which is not very elegant or useful  <_<

 

Rodrigo.

Link to comment
Share on other sites

Thanks Rodrigo. I know that the scrubber isn't being updated (hence the commented line).

 

The custom slider is cool but my pen was just a simplified example. My real question is about binding something to the timeline duration.

 

I'm beginning to think it's probably not as common as I thought and people just aren't doing it. 

Link to comment
Share on other sites

Hi,

 

I'm having a little trouble grasping the concept of binding something to a timeline's duration, could you be a little more specific about it?.I posted the codepen thinking that you are after similar to that, but seems that you're not.

 

Also keep in mind that as soon as you add a new instance to a timeline everything in it gets updated, duration, other instances or labels that could be nested, etc. So the new duration can be accessed just after you added any instance to it or created the timeline. Maybe I'm missing something here or my brain can't get around the concept you're after :|

  • Like 1
Link to comment
Share on other sites

I was just about to ask the same thing Rodrigo, about not understanding the concept of binding the duration to the timeline..

 

matthewwithanm, Do mean just having the input range slider have its value and / or max attribute synched with the tween? If you can explain a little bit more, or if you have an example of what your trying to achieve, it would be really helpful.. sorry, and thank you :)

Link to comment
Share on other sites

Data binding is a process for keeping the data in two different objects the same. When the data in one object changes, the data in the other is updated to reflect it. It depends on being able to observe changes (e.g. via events). Changes in Timeline durations aren't observable, though.

 

Note that when I say changes aren't observable I mean it in the conventional sense as in the observer pattern, Object.observe, etc. Obviously the new value can be retrieved with duration()—it's the change that can't be observed. I can't simply update my control whenever I add a tween because the tweens are added far away, in other objects, asynchronously.

Link to comment
Share on other sites

Hi Matthew,

 

Welcome to the GreenSock forums.

 

You bring up 2 valid requests that right now we don't offer a solution for.

 

Register multiple objects to listen for singular event

Since we do not have an event system in the traditional sense and we are just using callbacks, you can only assign one function per callback. 

 

onDurationChange Event or Callback

I can see how this could be useful for your situation, but its probably the first time I've heard anyone request it. 

 

We have probably received a few requests for a more robust event system, but not that many. The most popular request is probably for an onReverse callback / event. 

 

We've typically avoided a full blown event system due to file-size bloat and performance.

 

Again, it makes perfect sense for your situation to want this, and even the ON_DURATION_CHANGE event definitely would have a valid use in your scenario. The trouble is that it could be argued that every modification to a timeline (or tween) could benefit from a litany of events

 

ON_RESUME

ON_PAUSE

ON_REVERSE

ON_KILL

ON_TIMESCALE_CHANGE

ON_LABEL_ADDED

ON_LABEL_REMOVED

ON_NUMLABELS_CHANGED

ON_CHILD_ADDED

ON_NUMCHILDREN_CHANGE

ON_CHILD_REMOVED

ON_SHIFTCHILDREN

ON_RESTART

 

Again, your requests are totally valid, its just that we have to weigh pros and cons when implementing changes to the API. We offered real events in AS3 but from my experience in the forums, I can't really say it was a popular or highly used feature. 

 

We will definitely give this some further thought. There are 1 or 2 posts on here that more clearly describe why we don't use events.. but I'm having a tough time finding them at the moment.

 

Thanks for your questions.

  • Like 2
Link to comment
Share on other sites

Hi,

 

Thanks for clearing it up.

 

The only way I can think of checking constantly for changes in the duration without creating a specific code in order to report it to another part of the app, would be to attach an event listener to the tick event (which executes 60 times each second) to see if there's been any change in the timeline duration. If there's a change report it to other parts of the app. In any other order GSAP runs synchronously, so you have to take care of passing the data to an object so other element (Angular or any other MVC for that matters) can check and transmit for you.

 

The tick event could be like this:

//add listener
TweenMax.ticker.addEventListener("tick", myFunction);
 
function myFunction(event)
{
  // executes on every tick after the core engine updates
  // pass the new value to other elements of the app
}
Link to comment
Share on other sites

@Rodrigo Thanks for the suggestion, but I think that would have an undesirable performance impact.

 

@Carl Yeah, I can see how all those events would be problematic. Really even a generic "onChange" would be great—something that covered any non-playhead change to the Timeline (like addition, removal, etc). Perhaps it could implemented in a subclass or extension? Using JS modules might help with such a thing.

 

For the time being, though, I guess I'll have to give up on the idea of using a Timeline as a model.

 

Thanks for the responses all.

Link to comment
Share on other sites

Frankly, this strikes me as more of a code structure/architecture issue. Let me clarify a few things:

 

GSAP uses callbacks rather than event dispatching for 3 main reasons:

  1. Performance. Callbacks are much faster. 
  2. Memory (runtime and file size footprint). Event dispatching requires storing extra data in arrays internally plus creating an event object every time an event occurs which, for an animation platform, could easily be 60 times per second for each and every tween that's running (which could be hundreds or thousands). Ouch
  3. Callbacks typically make your code more concise. onComplete:myFunction vs. myTween.addEventListener("complete", myFunction);

You brought up the idea of implementing an onChange callback (or event) but doing so would require extra code in almost every function (duration(), timeScale(), pause(), resume(), delay(), startTime(), kill(), etc.) and we'd no doubt get complaints like "why does onChange only get dispatched on non-playhead stuff...why not when I alter the progress or seek()...?" There'd be a relatively high file size and performance cost that I'm not very comfortable with, especially considering how few people would likely ever use it. What if you tween the timeScale() to gradually slow a tween/timeline down? That'd fire the onChange on every single render.

 

Lastly, you said "I can't simply update my control whenever I add a tween because the tweens are added far away, in other objects, asynchronously" - if you don't mind, I'd like to respectfully challenge you on that. JavaScript by its very nature is not asynchronous. It is single-threaded and very much sequential in terms of execution (unless you're involving Web Workers which is almost never a great option in my experience). 

 

You've structured your code so that it's all feeding into a particular timeline and you're obviously associating it with the scrubber somehow, so it should be entirely possible to manage notifications/events however you please. For example, what if you created a single function through which all timeline additions flow, like:

function add(tween) {
    timeline.add(tween);
    updateScrubber(); //or dispatchEvent("change");
}

That way, GSAP remains super fast and lightweight, and you just wrap your functionality around it to get your event dispatching or communicate with whatever other modules you need in your own app. 

 

Basically, make it one layer of abstraction higher. 

 

The observer pattern makes a lot of sense in certain scenarios (like for browser/mouse events), but there are tradeoffs that make it a poor fit for things like an animation platform (at least in my opinion/experience). But even if it's not baked into the core platform itself, you can still wrap your own layer of abstraction around it to get exactly what you need. 

 

You should not need to "give up" on the idea of using Timeline as a model. 

 

Does that help?

  • Like 3
Link to comment
Share on other sites

Thanks for the response, Jack, but I understand the tradeoffs of events and callbacks, and how asynchronous functions work!


 


I think maybe I wasn't clear in my previous post; when I said I had to give up on the idea of using the Timeline as a model for my components, I meant doing exactly what you describe: exposing a different interface to the things that are currently given timelines.


Link to comment
Share on other sites

Cool. I understand. I hope my response didn't come across as insulting at all - clearly you know your stuff; sometimes I write responses in a way that attempts to help educate other people who stumble across the thread too rather than the just original person who asked the question. :)

  • Like 1
Link to comment
Share on other sites

No, no. I appreciate the feedback—and the library!

Link to comment
Share on other sites

Hi Matt.  For observable javascript, I recommend using the model-view-viewmodel design pattern and the knockout library.  If you're not familiar with it, knockout provides a great implementation of MVVM with observable properties and arrays.  You can write comprehensive view models and bind your views right up to it, and everything stays in sync.

 

The model that GSAP builds shouldn't be considered a viewmodel (ironic since it automates rendering).  Instead, you would write a view model as a go-between, with observables, and bind your controls to them.  Since the view updates the model through the view model, the view model knows when the model is changing, and can trigger a "proxy" observable in the view model that propagates changes throughout the view.  Think of the GSAP model as a single unit.  When any change is made to it, trigger the proxy observable to notifySubscribers (e.g. increment a number), and all your controls will update (provided they are bound to something that directly or indirectly subscribes to the proxy).

 

Hope this helps. 

  • Like 1
Link to comment
Share on other sites

Thanks for the suggestion Gary. I know about Knockout but I'm actually already using React and I don't want to mix the two. Either way, I will be wrapping the timeline instead of using it as a model directly.

Link to comment
Share on other sites

  • 3 months later...

I'm currently working on creating an Angular/GSAP tutorial that incorporates many of the things Matt asked about. I've created a playback service that can be injected anywhere your app, allowing you to manage/add tweens in different modules, watch/observe changes in the timeline, and broadcast events up and down your scope hierarchy.

 

Here's a demo of how it works with some simple animations

 

http://plnkr.co/edit/bG20DKs9GKGai29TsEEm?p=preview

 

Timeline duration, progress, speed, time, timeScale, paused, and reversed are all bound to the service, which will update the buttons, timeline status, and scrubber. Add as many animations as you want and then reset it to see the binding in action. The playback service also use the keyboard to quickly manipulate the timeline.

  • Like 3
Link to comment
Share on other sites

Hi OSUblake,

 

Your plunkr demo is excellent. Please let us know when your tutorial goes live.

Demos like this really expose how flexible and controllable GSAP timelines are. 

As a bonus its really nice to see more advanced and practical integration with Angular.

 

Carl

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