Jump to content
GreenSock

GoodMorning

Using States Conflicting with Animations

Go to solution Solved by GoodMorning,

Recommended Posts

Hi! I'm using GSAP with React + Typescript and have a basic animation where I move a few elements on the click of a button. However, I noticed that the animation glitches out when the button is pressed again before the animation has fully completed. So, I decided to add a state that would disable the button on the start of the animation, and re-enable it upon completion. However, this seems to have broken the animation to the point where it won't function at all. 

 

Thanks for any help!

 

Here's the function I use on button click: 

function animateButtonRight() {
        gsap.timeline({
            onStart: () => setRightDisabled(true),
            onComplete: () => {
                tempDiv = cardFiveRef;
                cardFiveRef = cardThreeRef;
                cardThreeRef = cardOneRef;
                cardOneRef = cardTwoRef;
                cardTwoRef = cardFourRef;
                cardFourRef = tempDiv;
                setRightDisabled(false);
            }
        })
            .to(cardThreeRef.current, { left: "", right: offscreenDistanceFromEdgeLR, bottom: offscreenDistanceFromEdgeBottom, rotation: 18, duration: 1.2, ease: Power3.easeInOut, transformOrigin: "right 50%" })
            .set(cardThreeRef.current, { left: offscreenDistanceFromEdgeLR, right: "", bottom: offscreenDistanceFromEdgeBottom, rotation: -18, transformOrigin: "left 50%" })
            .to(cardOneRef.current, { left: "", right: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: 9, duration: 1.2, ease: Power3.easeInOut, transformOrigin: "right 50%", delay: 0.2 }, 0)
            .to(cardTwoRef.current, { right: "", left: width / 2 - width * 0.22 / 2, bottom: distanceFromEdgeBottomM, rotation: 0, duration: 1.2, ease: Power3.easeInOut, delay: 0.4 }, 0)
            .to(cardFourRef.current, { right: "", left: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: -9, duration: 1.2, ease: Power3.easeInOut, delay: 0.6, transformOrigin: "left 50%" }, 0)
    }

 

Link to comment
Share on other sites

Very hard to debug without more context. If you have strict mode on, turn it off. Outside of that I don't know what you are trying to do, how you are trying to do it, or what is going wrong.

Link to comment
Share on other sites

I'm not trying much beyond attempting to animate divs on a button click, and disable the button on animation start + re-enable when the animation is complete. It's a simple timeline with changes to the right, left, bottom, and rotation properties. 

 

In this new component, the animation works with the lines in the onStart/onComplete commented out, but as soon as I attempt to set a state there, the animation doesn't run. 

 

I have a video of the behavior not working correctly when I use states: Link

 

I don't know if this is helpful but here is pretty much the complete context to the situation: 

function CardCarousel({ className }: Props) {

    // Middle Card
    let cardOneRef = useRef<HTMLDivElement>(null);
    // Left Card
    let cardTwoRef = useRef<HTMLDivElement>(null);
    // Right Card
    let cardThreeRef = useRef<HTMLDivElement>(null);
    // Left Offscreen Card
    let cardFourRef = useRef<HTMLDivElement>(null);
    // Right Offscreen Card
    let cardFiveRef = useRef<HTMLDivElement>(null);
    let tempDiv = useRef<HTMLDivElement>(null);
    let width = useWindowSize();
    
    const distanceFromEdgeBottomM = "-40%";
    const distanceFromEdgeLR = "5%";
    const distanceFromEdgeBottomLR = "-50%";
    const offscreenDistanceFromEdgeBottom = "-70%";
    const offscreenDistanceFromEdgeLR = "-20%";

    // Have this update on screen width change
    useEffect(() => {
        if (typeof window !== "undefined") {
            gsap.set(cardOneRef.current, { left: width / 2 - width * 0.22 / 2, bottom: distanceFromEdgeBottomM, rotation: 0 });
            gsap.set(cardTwoRef.current, { left: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: -9, transformOrigin: "left 50%" });
            gsap.set(cardThreeRef.current, { right: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: 9, transformOrigin: "right 50%" });
            gsap.set(cardFourRef.current, { left: offscreenDistanceFromEdgeLR, bottom: offscreenDistanceFromEdgeBottom, rotation: -18, transformOrigin: "left 50%" });
            gsap.set(cardFiveRef.current, { right: offscreenDistanceFromEdgeLR, bottom: offscreenDistanceFromEdgeBottom, rotation: 18, transformOrigin: "right 50%" });
        }
    }, [width])


    function animateButtonRight() {
        gsap.timeline({
            // onStart: () => setRightDisabled(true),
            onComplete: () => {
                tempDiv = cardFiveRef;
                cardFiveRef = cardThreeRef;
                cardThreeRef = cardOneRef;
                cardOneRef = cardTwoRef;
                cardTwoRef = cardFourRef;
                cardFourRef = tempDiv;
                // setRightDisabled(false);
            }
        })
            .to(cardThreeRef.current, { left: "", right: offscreenDistanceFromEdgeLR, bottom: offscreenDistanceFromEdgeBottom, rotation: 18, duration: 1.0, ease: Power3.easeInOut, transformOrigin: "right 50%" })
            .set(cardThreeRef.current, { left: offscreenDistanceFromEdgeLR, right: "", bottom: offscreenDistanceFromEdgeBottom, rotation: -18, transformOrigin: "left 50%" })
            .to(cardOneRef.current, { left: "", right: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: 9, duration: 1.0, ease: Power3.easeInOut, transformOrigin: "right 50%", delay: 0.1 }, 0)
            .to(cardTwoRef.current, { right: "", left: width / 2 - width * 0.22 / 2, bottom: distanceFromEdgeBottomM, rotation: 0, duration: 1.0, ease: Power3.easeInOut, delay: 0.2 }, 0)
            .to(cardFourRef.current, { right: "", left: distanceFromEdgeLR, bottom: distanceFromEdgeBottomLR, rotation: -9, duration: 1.0, ease: Power3.easeInOut, delay: 0.3, transformOrigin: "left 50%" }, 0)
    }


    return (
        <div className={classnames(styles.CardCarousel, className)}>
            <button onClick={animateButtonRight}>Move</button>
            <div ref={cardOneRef} className={styles.card}><h1>This is main initial text</h1></div>
            <div ref={cardTwoRef} className={styles.card}><h1>This is left initial text</h1></div>
            <div ref={cardThreeRef} className={styles.card}><h1>This is right initial text</h1></div>
            <div ref={cardFourRef} className={styles.card}><h1>This is offscreen left initial text</h1></div>
            <div ref={cardFiveRef} className={styles.card}><h1>This is offscreen right initial text</h1></div>
        </div>
    )
}

 

Link to comment
Share on other sites

  • Solution

Ok so Instead of using states, I simply used gsap.set(buttonRef.current, {disabled:true})
inside of the timeline which served the same purpose. I'm not sure how to handle states in the future, but this worked for now.

Link to comment
Share on other sites

Consider putting your animateButtonRight function in a useCallback. Currently, any time your component re-renders, it will recreate that function. Changing component state will cause that component to re-render. So every time you are changing state, you are creating a new animateButtonRight function with a new timeline, etc.

 

const animateButtonRight = useCallback(() => {
// gsap here
}, []) // <- empty dependency array so that your function never re-renders

While your solution to set the button to disabled works, it's better to handle this with state to keep the DOM and VDOM synced.

  • Like 2
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.
×