Jump to content
Search Community

Using States Conflicting with Animations

GoodMorning test
Moderator Tag

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

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

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.
×
×
  • Create New...