Jump to content
Search Community

Accessing Stagger Index in Callback

Brian Waters test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hello,

 

We're using stagger as a convenient way to control the timing of adding classes to DOM elements, in conjunction with other GSAP animation sequences. We'd like to be able to access the index of the current staggered item in callbacks. In trying both object and function versions, index doesn't seem to be readily available to callbacks - is there a property or utility method we're overlooking?

 

Below are 3 approaches that we've tried exploring. Note we're not actually tweening properties here, just hoping to leverage stagger's ability to distribute over time. Is that possible without writing a custom loop?

 

gsap.to(".productmenu-item", {
    stagger: {
        each: 0.1,
        onStart() {
            // Is there a way to access here the index value of the currently processing item?
            // this.targets()[0] returns the current targeted element but not its index in GSAP's array
        }
    },
});

 

gsap.to(".productmenu-item", {
    stagger: {
        each: 0.1,
        onStart: onMenuItemAnimationStart,
        onStartParams: [] // Can the current stagger index be passed as a parameter here?
    }
});

 

gsap.to(".productmenu-item", {
    stagger: function (index, target, list) {
        // The index is accessible here but would like to pass it to callbacks such as onStart and onComplete per element
        return index * 0.1;
    }
});

 

Thanks!

Link to comment
Share on other sites

I looked around and didn't find anything promising. As I'm sure you are aware this is a bit of a misuse of the library. You could force the matter, but you are better off writing your own loop. The stagger functionality is really simple to implement if all you need to do is have there be a time offset.

items.forEach((item, i) => setTimeout(yourFunction(), staggerAmount*i))

Link to comment
Share on other sites

  • Solution

I definitely wouldn't use a setTimeout() because that isn't synchronized with the tick/repaint cycle. It also wouldn't ensure that the particular tween had actually initialized, parsed its starting values, and began.

 

Here's what you could do: 

gsap.utils.toArray(".productmenu-item").forEach((item, i) => {
  gsap.to(item, {
    delay: i * 0.1, // <- stagger
    onStart() {
      console.log("index", i, "started:", item);
    },
    ...
  });
});

Does that help?

Link to comment
Share on other sites

If you want something reusable, here's a helper function...

function staggerTo(targets, vars) {
	let tl = gsap.timeline();
	targets = gsap.utils.toArray(targets);
	if (typeof(vars.stagger) === "object") {
		let staggerVars = Object.assign({}, vars.stagger),
			each = staggerVars.amount ? staggerVars.amount / targets.length : staggerVars.each || 0,
			wrap = (func, i, target) => () => func(i, target, targets),
			types = "onComplete,onStart,onUpdate,onReverseComplete".split(","),
			tweenVars = Object.assign({}, vars), callback;
		staggerVars.each = staggerVars.amount = tweenVars.stagger = 0;
		"repeat,repeatDelay,yoyo,repeatRefresh,yoyoEase".split(",").forEach(n => staggerVars[n] && (tweenVars[n] = staggerVars[n]));
		targets.forEach((target, i) => {
			let v = Object.assign({}, tweenVars);
			types.forEach(type => staggerVars[type] && (v[type] = wrap(staggerVars[type], i, target)));
			tl.to(target, v, i * each);
		});
	} else {
		tl.to(target, vars);
	}
	return tl;
}

 

Usage:

staggerTo(".box", {
	x: 500,
	duration: 1,
	stagger: {
		each: 0.5,
		repeat: 2,
		yoyo: true,
		onStart(i, target, targets) {
			console.log(i, "of", targets.length, "started", target);
		},
		onComplete(i, target, targets) {
			console.log(i, "of", targets.length, "completed", target);
		}
	}
});

 

Demo:

See the Pen PoRweXE?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Sorry, I couldn't help myself. I like making things easier for folks with helper functions like this. ;)

  • Like 2
Link to comment
Share on other sites

  • 3 months later...

Hi,

This works for me:

 

const indicator = document.querySelector('.timeline__indicator');

  const tl = gsap.timeline({
    scrollTrigger: {
      trigger: '.timeline',
    },
  });

  const stagger = 0.75;

  tl.from('.timeline__item', {
    scale: 1,
    opacity: 0,
    stagger: {
      each: stagger,
      onStart() {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        const element = this.targets()[0];
        gsap.to(indicator, {
          top: element.offsetTop,
          height: element.offsetHeight,
          duration: 0.3,
        });
      },
    },
  }).to(
    '.timeline__item',
    {
      scale: 1,
      stagger,
    },
    stagger
  );

The problem is I have to add the // @ts-ignore comment because targets function is not exists in this context, but it exists in the onStart function inside  a tween.

 

For instance: 

  tl.from('.timeline__item', {
    scale: 1,
    opacity: 0,
    stagger: {
      each: stagger,
      onStart() {
        // Property 'targets' does not exist on type 'Number | FunctionBasedValue<number> | StaggerVars'.
        this.targets()[0];        
      },
    },
  }).to(
    '.timeline__item',
    {
      scale: 1,
      stagger,
      onStart() {
        // Here it works
        this.targets()[0];        
      },
    },
    stagger
  );

Why this happens? Any solution?

Link to comment
Share on other sites

Hi Cassie, 

 

My question is not related to Typescript itself, but why GSAP type declarations does not have the targets function when the onStart function is inside staggers, but it works when it is inside .from or .to? Although in both cases it works and the target is found.

  • Like 1
Link to comment
Share on other sites

3 hours ago, Antonio Giroz said:

My question is not related to Typescript itself, but why GSAP type declarations does not have the targetsfunction when the onStartfunction is inside staggers, but it works when it is inside .fromor .to? Although in both cases it works and the target is found.

I'm not a TypeScript expert either, but I'm a bit confused by your question because "this" in the onStart() refers to the Tween instance EVERYWHERE (not just in to() or from() or fromTo()...it's identical in the stagger's onStart). If you know how to edit the TypeScript definition file to accommodate your request, please let us know. 🤷‍♂️

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