Elusien Posted November 13, 2022 Posted November 13, 2022 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}, ">")
Solution Rodrigo Posted November 14, 2022 Solution Posted November 14, 2022 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! 1
Elusien Posted November 14, 2022 Author Posted November 14, 2022 @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. 2
Elusien Posted November 14, 2022 Author Posted November 14, 2022 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.
GreenSock Posted November 14, 2022 Posted November 14, 2022 @Elusien, here's a strategy you could use: See the Pen vYrZMGa?editors=1010 by GreenSock (@GreenSock) on CodePen. Does that help? 1
Elusien Posted November 14, 2022 Author Posted November 14, 2022 Brilliant! That is exactly what I need. Thank you for coming up with this solution. 1
fcdobbs Posted May 6, 2023 Posted May 6, 2023 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!
GreenSock Posted May 6, 2023 Posted May 6, 2023 @fcdobbs I think you could greatly simplify things: See the Pen NWOyGWr?editors=0010 by GreenSock (@GreenSock) on CodePen. Is that what you're looking for?
fcdobbs Posted May 8, 2023 Posted May 8, 2023 Works like a charm! Thanks again for all of your help. 1
fcdobbs Posted May 12, 2023 Posted May 12, 2023 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
Rodrigo Posted May 12, 2023 Posted May 12, 2023 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 ?♂️
fcdobbs Posted May 13, 2023 Posted May 13, 2023 Yes. Thanks. That's my question. What's the correct syntax to pass the index value of the target to the onStart function of the stagger object or reference the index value in the stagger object?
Rodrigo Posted May 15, 2023 Posted May 15, 2023 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!
fcdobbs Posted June 3, 2023 Posted June 3, 2023 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. 1
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now