Jump to content
Search Community

How to get staggers to end simultaneously

Elusien test
Moderator Tag

Go to solution Solved by Rodrigo,

Recommended Posts

I have a timeline tween that uses "stagger".  The starts of the animations for each element will be staggered, but so will the ends of the animations. I would like the ends of the animations to happen simultaneously. Is there an option for this?

e.g.

The 1st element starts animating at 0 sec and finishes after 5 seconds (time: 0sec to 5sec, duration 5secs);

The 2nd element starts animating at 1 sec and finishes after 4 seconds (time: 1 sec to 5sec, duration 4 secs);

The 3rd element starts animating at 2 sec and finishes after 3 seconds (time: 2 sec to 5sec, duration 3 secs);

 

My current code (which does not do this) is:

 

   tl .to  (".circle", {"background-color": to_color, stagger: {amount: 5.0, grid: [nh, nw], from: stagger_from}, duration: 0}, ">")
Link to comment
Share on other sites

  • Solution

Hi @Elusien and welcome to the GreenSock forums!

 

As far as I can tell, the Staggers API doesn't include such tooling because we're talking about the duration of the animation with an equal stagger time. For that I can think of two alternatives.

 

Create a timeline and loop through the elements and change the duration of each animation on each loop:

const boxes = gsap.utils.toArray(".box");
const duration = boxes.length;
const tl = gsap.timeline({ paused: true });
boxes.forEach((box, index) => {
  tl.to(box, {
    y: 200,
    rotation: 360,
    ease: "none",
    backgroundColor: "#ff00ff",
    duration: duration - index,
  }, index ? "<=+1" : 0);
});

Create a single GSAP instance and use a function based value for the duration running basically the same code:

const boxes = gsap.utils.toArray(".box");
const duration = boxes.length;
const tl = gsap.to(boxes, {
  stagger: 1,
  y: 200,
  rotation: 360,
  backgroundColor: "#ff00ff",
  ease: "none",
  duration: (index) => duration - index,
});

Here is a live example with both approaches (just comment one and uncomment the other) and as you can see the result is the same:

See the Pen dyKRXgZ by GreenSock (@GreenSock) on CodePen

 

Hopefully this is what you're looking for. Let us know if you have more questions.

 

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

@Rodrigo Thank you for providing this solution. I find a lot of staggered animations look better when they terminate simultaneously. At present I am working on creating animated masks for video editing and would like to try your solution on some of the ones I have already done without the co-ordinated ending and see how well they work

 

I've been using GSAP on and off for some while, and find it amazing. I keep forgetting you can use functions and not just variables.

 

Thanks again.

  • Like 2
Link to comment
Share on other sites

I think I am going to have to go down the route of looping over each element since the "stagger" solution will not work for 'from: "random"', since the indexes always appear as 0 to N-1, not the random order. It's a pity as now I have to write the code to generate my own "random", "edges", "center" etc... It isn't insurmountable, but it is a bit fiddly.

 

Thanks again, for setting me on the right track though.

Link to comment
Share on other sites

  • 5 months later...

Hi,

 

I'd like to modify this "stagger from end of timeline" solution to use a startTime pulled from an array.

 

I'm generating the duration for each element and total duration, like this:

 

randomSineOut = () => Sine.easeOut(Math.random())

elemDurationMin = 1

elemDurationMax = 4

 

let elemDurationAr = [], totalDuration = 0

for (h=0; h<elemCount; h++) {

  elemDurationAr[h] = elemDurationMin+(elemDurationMax-elemDurationMin)*randomSineOut()

  totalDuration = Math.max(elemDurationAr[h],totalDuration)

}

 

For each tween I'd like to set the duration = elemDurationAr[index] and the startTime = totalDuration - elemDurationAr[index].

 

I believe modifying the tween duration has to be done from the stagger object.

 

I'm having trouble figuring out how to pass these values to the stagger object and its onStart function:

See the Pen qBJxWOP?editors=0011 by fcdobbs (@fcdobbs) on CodePen

 

Any help would be appreciated.  Thanks!

Link to comment
Share on other sites

It looks like I still need to figure out how to make the startTime and durations both functions of the target index without iterating over the elements in order to animate non-numeric properties.

I couldn't find any syntax that makes the stagger value a function inside the stagger object.

I also couldn't figure out how to reference the index value of the target.

The onStart function of the stagger object has access to the arrays defined in the parent scope, so I thought if I can reference the target index value that I might be able to set amount to 0 and defined a delay and duration that is a function of the target index, like this:

See the Pen oNayyxj?editors=0011 by fcdobbs (@fcdobbs) on CodePen

 

What's the right way to define a function for the duration of each stagger and have the staggers all end at the same time?

 

Thanks

Link to comment
Share on other sites

Hi,

 

There is an error in your codepen:

function animateFlex_stagger_object(){
  let state = Flip.getState(boxes);
  gsap.set(boxes, {position: "static", left: "auto"});
  Flip.from(state, {
    duration: totalDuration,
    delay: 0,
    ease: "bounce.out",
    stagger: {
    amount:  0,
    onStart: function(e,i) {
      // "this" refers to the individual child tween. DON'T use an arrow function, or the scope ("this") would be wrong. 
      this.duration(durations[i])
      this.delay(totalDuration-durations[i])
      
    },
     onStartParams: [e, i],
    }
  });  
}

Neither e nor i are defined in that function, so nothing happens 🤷‍♂️

Link to comment
Share on other sites

Hi,

 

Indeed the Stagger API doesn't have function based values for those parameters but you can set a stagger value using a function in this case:

const durations = boxes.map(
  (box, index) =>
  positions[index] +
  gsap.utils.mapRange(0, 1, minDuration, maxDuration, sine(Math.random()))
),
      totalDuration = Math.max(...durations), // find the biggest duration
const randomDelay = gsap.utils.random(0, totalDuration / 5, 0.1, true);
const times = boxes.map((e, i) => {
  const delay = randomDelay();
  return {
    duration: totalDuration - delay,
    delay,
  };
})

function animateFlex_stagger_object() {
  let state = Flip.getState(boxes);
  gsap.set(boxes, { position: "static", left: "auto" });
  Flip.from(state, {
    duration: (i) => times[i].duration,
    delay:  0,
    ease: "bounce.out",
    stagger: (i) => times[i].delay,
  });
};

Another option would be to run a loop for all the boxes and create a Flip instance for each adding a specific delay for each one:

function animateFlex_stagger_object() {
  boxes.forEach((box, i) => {
    const state = Flip.getState(box);
    gsap.set(box, { position: "static", left: "auto" });
    Flip.from(state, {
      duration: times[i].duration,
      delay:  times[i].delay,
      ease: "bounce.out",
    });
  });
};

Here is a fork of your codepen showing the first approach:

See the Pen rNqrRZL by GreenSock (@GreenSock) on CodePen

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

  • 3 weeks later...

Thanks, Rodrigo.

 

Both of these options work well.  I'm surprised that iterating through the elements works with flexbox animations.  I thought that what was going wrong in my first attempts was that the final position of the boxes isn't known until all of the boxes are updated to static position. 

 

The loop structure also works!

 

Thanks again.

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