Jump to content
Search Community

Timeline with ScrollTrigger, cancel previous animations in queue

BrandonI test
Moderator Tag

Go to solution Solved by GreenSock,

Recommended Posts

Hey Guys,

 

This is my first foray into using gsap and i'm really enjoying it so far. One of the questions that I can't seem to find through the forums is cancelling a queue of a timeline. I'll give an example with what I'm trying to do.

 

I have a trigger to to hide my navigation and display a menu when a user scrolls down a page. I'm using the onEnter & onLeaveBack methods to trigger animation events. I noticed if I scroll up and down quickly enough (many times) - the animation queue will play through all the events - as expected.

 

I was wondering whats the best way to prevent the entire queue of animations to play through multiple times and just play the last item in the animation event stack. I'm assuming its using the kill() method, but I haven't found the best place to put it. For context, i'm using react (hence the refs) inside a useEffect, here's just the actual timeline.

navTimelineRef.current = gsap.timeline({
                scrollTrigger: {
                    trigger: document.body,
                    start: `${window.innerHeight / 2} top`,
                    end: 'max',
                    markers: true,
                    fastScrollEnd: true,
                    invalidateOnRefresh: true,
                    onEnter: () => {
                        // Nav items stagger away
                        navTimelineRef.current.to(navRef?.current?.children, {
                            autoAlpha: 0,
                            stagger: 0.2
                        });

                        // hamburger nav comes into view
                        navTimelineRef.current.to(hamburgerRef.current, {
                            autoAlpha: 1
                        });

                        // logo icon scales up
                        navTimelineRef.current.to(hamburgerLogoRef.current, {
                            scale: 2
                        });
                    },
                    onLeaveBack: () => {
                        // logo icon scales back down
                        navTimelineRef.current.to(hamburgerLogoRef.current, {
                            scale: 1
                        });

                        // hide the hamburger menu
                        navTimelineRef.current.to(hamburgerRef.current, {
                            autoAlpha: 0
                        });

                        // reverse stagger back in the menu items
                        navTimelineRef.current.to(navRef.current.children, {
                            autoAlpha: 1,
                            stagger: 0.2,
                            reversed: true
                        });
                    }
                }
            });

 

Appreciate any feedback, thanks!

Link to comment
Share on other sites

  • Solution

Welcome to GSAP, @BrandonI

 

It looks like you're reusing the same timeline over and over, continuing to add animations to it so it keeps getting longer and longer. That's actually pretty wasteful because not only do you end up with a super long timeline eventually, but those animations aren't released for garbage collection because you're telling GSAP that you want to keep them in that timeline and it has no idea if you might want to call reverse() at some point and play EVERYTHING backwards. 

 

It also looks like you're using React. It's always best to provide a minimal demo so that we can see the issue in context (like a Stackblitz or CodeSandbox), but I'd highly recommend reading this article:

 

gsap.context() is your new best friend in React because it makes cleanup so simple, plus it gives you selector scoping (saves you from having to create a ref for every element you want to animate). 

 

You could just create a fresh timeline in your onEnter/onLeave. Kill() the old one to avoid conflicts (like if the user scrolled up/down/up/down really fast over the start point, you don't want to end up with 4 timelines all animating the same properties of the same elements). So your code might look something like this: 

useLayoutEffect(() => {
	let ctx = gsap.context(() => {
		let navTL; // keep track of the most recent animation so we can easily kill() it
		
		ScrollTrigger.create({
			trigger: document.body,
			start: `${window.innerHeight / 2} top`,
			end: 'max',
			markers: true,
			onEnter: () => {
				navTL && navTL.kill(); // if there's an animation, kill it so we don't create conflicts
				navTL = gsap.timeline();
				// Nav items stagger away
				navTL.to(navRef?.current?.children, {
					autoAlpha: 0,
					stagger: 0.2
				});

				// hamburger nav comes into view
				navTL.to(hamburgerRef.current, {
					autoAlpha: 1
				});

				// logo icon scales up
				navTL.to(hamburgerLogoRef.current, {
					scale: 2
				});
			},
			onLeaveBack: () => {
				navTL && navTL.kill(); // if there's an animation, kill it so we don't create conflicts
				
				// logo icon scales back down
				navTL.to(hamburgerLogoRef.current, {
					scale: 1
				});

				// hide the hamburger menu
				navTL.to(hamburgerRef.current, {
					autoAlpha: 0
				});

				// reverse stagger back in the menu items
				navTL.to(navRef.current.children, {
					autoAlpha: 1,
					stagger: 0.2
				});
			}
		});
		
	});
	
	return () => ctx.revert(); // <-- CLEANUP!
});

I hope that helps! If you need more assistance, please make sure you provide a minimal demo. Here is a React starter template you can fork. 

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