Jump to content
Search Community

Hover state of custom cursor has unpredictable behaviour (React / GSAP)

Aimack test
Moderator Tag

Recommended Posts

I've created a custom cursor whose X and Y gets updated on change of mouse. Using React and GSAP.
The cursor also changes in size when it is hovered on a specific text. However, the hover state is not consistent and glitches at the time of using it.

I'm sharing the codesandbox link where the same problem is replicated. Any and every help is appreciated, thank you.

 

Codesandbox Link: Link

Link to comment
Share on other sites

Hi,

 

I think you are overcomplicating this by using state on a parent component only to pass that state to a second child component. You should use React Context for that instead of use the props-drilling approach:

https://react.dev/learn/managing-state#passing-data-deeply-with-context

 

https://react.dev/reference/react/createContext

 

Also the approach of using state to update the mouse follower position. That is completely unnecessary because there is not really need to re-render the component every time the entire component every time the mouse moves, right?

 

I think the approaches in this thread can provide a better starting point for this:

The examples there use GSAP quickTo() method:

https://gsap.com/docs/v3/GSAP/gsap.quickTo()

 

I think this is a better approach IMHO:

import { useEffect, useLayoutEffect, useRef } from "react";
import Styles from "./cursor.module.css";
import gsap from "gsap";

// eslint-disable-next-line react/prop-types
function Cursor({ hover }) {
    const cursorRef = useRef(null);
    const ctx1 = useRef(null);
    const cursorSize = 30;
    const mouseMovehandler = (e) => {
        const { clientX, clientY } = e;
        ctx1.current.mouseMove(
            clientX - cursorSize / 2,
            clientY - cursorSize / 2
        );
    };

    useEffect(() => {
        ctx1.current = gsap.context((self) => {
            const xTo = gsap.quickTo(cursorRef.current, "x", {duration: 0.6, ease: "power3"});
            const yTo = gsap.quickTo(cursorRef.current, "y", {duration: 0.6, ease: "power3"});
            self.add("mouseMove", (x, y) => {
                xTo(x);
                yTo(y);
            });
            const scaleTween = gsap.to(cursorRef.current, {
                scale: 2,
                duration: 0.05,
                paused: true,
            }).reverse();
            self.add("grow", (value) => scaleTween.reversed(!value));
        });
        window.addEventListener("mousemove", mouseMovehandler);
        return () => {
            ctx1.current.revert();
            window.removeEventListener("mousemove", mouseMovehandler);
        }
    }, []);

    useLayoutEffect(() => {
        ctx1.current && ctx1.current.grow(hover);
    }, [hover]);

    return (
        <div
            className={Styles.cursor}
            ref={cursorRef}
        ></div>
    );
}

export default Cursor;

https://codesandbox.io/p/github/insightofakash/imgonhover/csb-rdqyhn/draft/xenodochial-moon

 

The blend mode doesn't work, unfortunately we don't have the time resources to tackle everything, so I'll leave that to you.

 

Hopefully this helps.

Happy Tweening!

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Hi,

 

The demo seems to be public, I can access it on an incognito window on the browser:

https://codesandbox.io/p/github/insightofakash/imgonhover/csb-rdqyhn/draft/xenodochial-moon?file=%2Fsrc%2Fcomponents%2Fcursor%2FCursor.jsx%3A1%2C1

 

Here is the code of the cursor file just in case:

import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Styles from "./cursor.module.css";
import gsap from "gsap";

// eslint-disable-next-line react/prop-types
function Cursor({ hover }) {
    const cursorRef = useRef(null);
    const [mousePosition, mousePositionChange] = useState({ clientX: 0, clientY: 0 });

    const cursorSize = "30";

    useEffect(() => {
        window.addEventListener("mousemove", (e) => {
            mousePositionChange({ clientX: e.clientX, clientY: e.clientY });
        });

        return () => window.removeEventListener("mousemove");
    }, []);

    useLayoutEffect(() => {
        let ctx1 = gsap.context(() => {
            gsap.to(cursorRef.current, {
                x: mousePosition.clientX - cursorSize / 2,
                y: mousePosition.clientY - cursorSize / 2,
                ease: "back.out(1.8)",
            });

            // console.log("Position Update");

            return () => ctx1.revert();
        });
    }, [mousePosition]);

    useLayoutEffect(() => {
        let ctx2 = gsap.context(() => {
            hover
                ? gsap.to(cursorRef.current, {
                      height: cursorSize * 2,
                      width: cursorSize * 2,
                      duration: 0.05,
                  })
                : gsap.to(cursorRef.current, {
                      height: cursorSize,
                      width: cursorSize,
                      duration: 0.05,
                  });

            console.log("Size Update");

            return () => ctx2.revert();
        });
    }, [hover]);

    return (
        <div
            className={Styles.cursor}
            ref={cursorRef}
        ></div>
    );
}

export default Cursor;

The issue could be the fact that the scale up/down is triggered constantly. Yeah I don't have time to make that work with the blend mode. There must be a way, but the mouse follow logic is far cleaner and simpler.

 

Happy Tweening!

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