Jump to content
GreenSock

Dave Stewart

Propagating a child timeline pause to the parent timeline

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 have a parent timeline which has a bunch of child timelines added.

 

Each child timeline (scene) has to trigger a pause of the main timeline halfway through so that the app can display some information about the current scene.

 

In essence, it's this:

main    ----+---------+---------+---->
sceneA  ----^---->
sceneB            ----^---->
sceneC                      ----^---->

Each scene timeline is built separately, then added to the main timeline.

 

At the moment, I'm using a .call() from each child timeline to an intermediate function to instruct the main timeline to pause. However, there seems to be a slight delay on this, so the pause will happen a few frames off when I actually need the pause, so on-screen elements will no longer be precisely aligned.

 

I've considered various ideas, but all seem to be either:

  • code-heavy: setting child labels, calculating time offsets, seeking the main timeline, or
  • hacky: instructing the child timeline to wait or even pause itself, then pausing the main timeline

What's the best way to do this?

 

Cheers,

Dave

Link to comment
Share on other sites

Here's a Pen of the first method:

 

See the Pen eHlit?editors=101 by davestewart (@davestewart) on CodePen

 

It's perfectly valid code, but feels like a lot of code for something that should perhaps be in the core API.

 

I'm considering adding some kind of pauseParent() function to the Prototype that would wrap all this up in a method, and allow me to pass an optional callback (seeing as the built-in pause() doesn't allow you to):

tl.pauseParent(tlMain, callback);

Hopefully there's some hidden feature I've missed though and someone's about to tell me what it is (as usual :) )

Link to comment
Share on other sites

Grrr. This doesn't work on a more complex timeline!

 

There seems to be a delay of 100-200ms so the animation, even though it is supposed to have seeked, is still past the point of the supposed pause.

 

They seem to be rounding errors, as telling the main timeline to seek to the rounded value via the console sorts everything out.

 

The current child timeline is the 7th added to the main one, if that helps.

 

Is there any way round this?

 

Currently I'm having to add dummy-time round each of the pauses so that the scene doesn't appear to animate-on.

Link to comment
Share on other sites

Hi Dave,

 

I haven't yet had time to process your latest 'Grrr', but to rewind a bit.

 

First the discrepancy you were originally seeing when using call() to pause() the parent timeline is undesired but not entirely unexpected simply because there is always going to be some time that transpires between when you call a function and the time it actually executes. 

 

This is actually the reason we created the addPause() method as previously folks were using call() to self-pause timelines as they were running and we noticed the time drift.

 

Working within the limitations of the current API I would suggest that when you add your scenes to the story timeline, you place addPause() methods into story. This seems to work fine and doesn't seem to be overly verbose or hacky.

 

function insertScene(element) {
  var newScene = scene(element) // grabs the scene 
  var insertionTime = story.duration() // figure out where it should go
  story.add(newScene, insertionTime) // add the scene
  story.addPause(insertionTime + newScene.getLabelTime('pause')) // add the pause
}

insertScene(document.getElementById("box1"));
insertScene(document.getElementById("box2"));

http://codepen.io/GreenSock/pen/Bgtaw?editors=001

 

Does it work? Yes... at least i'm pretty sure;)

Is it the most flexible solution? Certainly not, but it wouldn't be too hard to handle multiple pauses within a scene.

 

 

Don't get me wrong, I can see where the pauseParent() could be very useful in this particular scenario. I'm sure Jack will think it through a bit more as its important to consider the implications beyond just the immediate problem that it solves. Frankly, I wonder if things could spiral out of control if we start adding methods to timelines that are supposed to control other timelines. 

 

--

 

As for that rounding error you encountered, I'm sure if we can replicate it easily we can figure something out. 

 

Thanks again for your suggestions.

Link to comment
Share on other sites

note: 2 seconds after posting, CodePen is blowing up on me :( grrrr.

Seems it was refusing to load your controller. Something similar to what Jonathan was seeing a few days ago. So something may be odd if you test my latest example. Stand by...

Link to comment
Share on other sites

of course. I removed the controller init code when hastily cleaning things up. DOH!

Link to comment
Share on other sites

I should add the new Controller code at some point - it's more robust now!

 

OK, I see what you're doing there. The trick seems to be adding the pause to the main timeline only *after* you've added the child timeline, as otherwise, from memory, the pause would have already changed the main timeline's duration, and the child would be added at the end, right?

 

That's a good enough workaround for now, I'll run with that. 

 

In fact, my main Story object which manages all the child timelines could easily be responsible for adding pauses to the main story timeline. Just after initialization, it could loop over all the added child timelines, look for 'pause' labels, then add pauses to the main timeline. I guess I could wrap all this in a prototype method that would allow callbacks to be stored on the vars object as well, so in the final sweep, they would already referenced for calling at the correct time. 

 

It is all getting a bit spaghetti-like though.

 

Would love to see some kind of OO .addRootPause().

 

To be honest, I've added references in my childTimeline vars objects to their parents anyway. Is this impossible / disadvantageous in the API?

 

Also, are there situations where it's actually *appropriate* to pause child timelines? I've done by accident a couple of times, and it seems to produce pretty undesirable effects. That's not to say there won't be use-cases, of course.

I could imagine that pausing looping child timelines would be useful, but I remember reading that adding a looping child timeline essentially sets the parent timeline's duration to infinity (which is kinda bad) is that right?

Answers on a postcard please...

Link to comment
Share on other sites

FYI, this is the animation: http://g4s.davestewart.co.uk/

 

The animation-specific files are:

  • story.js - manages the main timeline, locations, scenes, and dependancy-injection
  • scene.js - a wrapper around child timelines and their elements
  • main.js - where all locations, scenes and animation are configured
  • functions.js - helper and prototype functions 

It's pretty much the same approach used on the GSAP homepage, but with more structure.

Link to comment
Share on other sites

Nice work Dave!!!

  • Like 1
Link to comment
Share on other sites

 The trick seems to be adding the pause to the main timeline only *after* you've added the child timeline, as otherwise, from memory, the pause would have already changed the main timeline's duration, and the child would be added at the end, right?

 

 

Yes, the addPause() will affect the duration of the timeline. The way I approached this though, the addPause() does not have to go in after the story is added because I am using the insertionTime variable to hold a reference to the duration.

 

Both of these will work the same

 

add scene then pause

var insertionTime = story.duration() // figure out where it should go
  story.add(newScene, insertionTime) // add the scene
  story.addPause(insertionTime + newScene.getLabelTime('pause')) // add the pause

add pause then scene

var insertionTime = story.duration() // figure out where it should go
  story.addPause(insertionTime + newScene.getLabelTime('pause')) // add the pause
  story.add(newScene, insertionTime) // add the scene

---

 

Just after initialization, it could loop over all the added child timelines, look for 'pause' labels, then add pauses to the main timeline. 

 

 

yes, that would work perfectly fine.

 

To be honest, I've added references in my childTimeline vars objects to their parents anyway. Is this impossible / disadvantageous in the API?

 

 

That should be fine. The base Animation class contains a data object which is meant for storing any extraneous data. So you could do

 

var scene = new TimelineMax({data:{name:el}});
//and then later
console.log(scene.data.name);
and yes, you can pause child timelines. i'm sure some folks have found that useful. Not sure what undesirable effects that had for you. And yes if you add an infinitely repeating timeline to a parent timeline it will make the parent go forever and not have an end (well at least not for 30,000 years), so yes that can be a little difficult to work with some times. 
 
 
--
 
Nice animation! thanks for sharing. 
  • Like 1
Link to comment
Share on other sites

Hey Carl,

 

The undesirable effects were just the main timeline looking severely out of sync as it played through.

 

And what I meant by before/after with the insertion was more that the physical act of adding a pause lengthens the timeline to that point. I get that you can add in either order if you are specific about it though.

 

Thanks for the clarifications anyway :)

  • Like 1
Link to comment
Share on other sites

Would you adam and eve it. Still getting the same issue with the root timeline pause - you can see here that the next immediate operation in the child timeline, a fade in, is still triggering:

 

7hALsnR.png

 

I can even manually .addPause() the main timeline, using the time calculated by duration + label.time, and I get the same result.

Link to comment
Share on other sites

Sorry, I don't understand what that means. What are the actionable steps to reproduce the problem? Can you provide a CodePen that shows that addPause() is not working?

Link to comment
Share on other sites

I wish I could, but that would be a bit tricky.

 

The code is essentially:

// child timeline
tl
  // add a load of other tweens
  .to(van, 10, {x:500}) // completes at 37.206 (global time)
  .to(scene, 1, {autoAlpha:0});

// add to main timeline
tlMain
  // add a load of other timelines
  .add(tl);

// main timeline
tlMain
  .addPause(37.206); // get the result as illustrated above
Link to comment
Share on other sites

This Pen forks yours and adds 50 child timelines with root pauses, and does it flawlessly:

 

See the Pen gBplw by davestewart (@davestewart) on CodePen

 

Somehow a delay is creeping into mine. I will have to dig about.

  • Like 1
Link to comment
Share on other sites

I think I understand what you're asking for here, Dave, and Carl is correct about it not being a bug, but rather a natural consequence of how iteratively-updated animation works. If the addPause() docs don't explain it sufficiently, let me know and I'll give it a crack here. 

 

Nevertheless, I want to get you up and running with the whole "have a deeply nested timeline pause an ancestor timeline at a particular spot with exact precision" thing. At first, I wrote a single method that'd accomplish it for you, but then I split it apart into more modular functions to make it a little more flexible. Here's the code:

//used by the addPause() method - this does the actual pausing at a precise time.
function pauseCallback(tween, callback, params, scope, animation) {
	//figure out where the tween's exact startTime is globally and convert it to the local time of the supplied animation (typically a timeline, but it could be any animation). Remember, this callback may technically be rendered slightly late (like 0.01 seconds), so we don't want to just pause() now - we want to rewind to the precise time that it's supposed to be.
	animation.pause( globalToLocal(localToGlobal(0, tween), animation) );
	if (callback) {
		callback.apply(scope || tween.timeline, params || []);
	}
}

//translates a local time inside an animation to the corresponding time on the root/global timeline, factoring in all nesting and timeScales.
function localToGlobal(time, animation) {
	while (animation) {
		time = (time / animation.timeScale()) + animation.startTime();
		animation = animation.timeline;
	}
	return time;
}

//translates the supplied time on the root/global timeline into the corresponding local time inside a particular animation, factoring in all nesting and timeScales
function globalToLocal(time, animation) {
	var scale = 1;
	time -= localToGlobal(0, animation);
	while (animation) {
		scale *= animation.timeScale();
		animation = animation.timeline;
	}
	return time * scale;
}

//we can edit the TimelineLite's prototype to bring this new functionality to all addPause() calls which would now accept a 5th paramter for defining which animation you'd like paused.
TimelineLite.prototype.addPause = function(position, callback, params, scope, animation) {
	return this.call(pauseCallback, ["{self}", callback, params, scope, animation || this], this, position);
};

Usage:

timeline.addPause(time, callback, params, scope, animationToPause)

Notice that you can now pass a 5th parameter to define which animation you want paused. Or you can use the methods I provided above to build your own functionality, like perhaps automatically finding the top timeline (below the root) and pausing that one rather than having to pass that in as a parameter. Again, I think I provided everything in a manner that should give you ultimate flexibility to accomplish whatever you want. 

 

Does that help? 

  • Like 4
Link to comment
Share on other sites

Jack,
 
Wow, those functions look like exactly what would solve Dave's initial problem of pausing a parent from a child. 
 
Along the way I suggested he use addPause() only on the parent timeline. it seems he has run into a situation where addPause(someTime) is not properly stopping at someTime. He is in the process of trying to figure out if someTime is being miscalculated on his end, ours or the browsers. 

 

 

Dave,

 

Thanks for taking the effort to try to replicate addPause() not working. It will be very helpful to know if

 

  • its being inserted at the proper time but not stopping there
  • somehow along the way the engine is miscalculating the duration() of the timeline causing the addPause() to be inserted at the wrong spot
  • maybe something in your code is attributing to some errors

The only thing I can suggest is perhaps logging as much as possible when timelines are created and inserted like keep track of

  • duration() of the scene you are creating
  • the startTime() of the scene once its added to the story
  • the duration() of the story every time a scene is added

We have seen instances where the browser does some funky rounding that throws things off and occasionally there will be a bug on our end so anything you can do to help us understand where / how this is happening would be helpful.

 

---

  • Like 2
Link to comment
Share on other sites

Thanks Jack!

 

That looks like it should be just the ticket.

 

I've just implemented it into the animation with mixed results though. One part of the timeline paused and unpaused fine. Another, when resuming, seemed to jump back to a previous point in the animation - I'm not sure why.

 

I'm still getting some other bits sorted, so I'll come back to this and give it some more testing at a later point.

 

Really appreciate you taking the time and trouble to understand my requirements and work towards implementing a fix though.

 

:D

Link to comment
Share on other sites

No problem, Dave. I'd love to see a simple demo of the problem you mentioned (jumping back to a previous point upon resume). I'm not aware of any such bugs, nor can I imagine what might be causing the problem inside GSAP (I wonder if perhaps you've got some code that's causing the jump?) Of course it very well could be a problem in GSAP - I just need some way of reproducing the problem in order to troubleshoot. 

 

Thanks Dave. 

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