Search code examples
htmlcssreactjsframer-motion

Animating position of radial gradient using framer moiton


So I have this component which aims to add a sort of spotlight effect to a paragraph/text. I've used a radial gradient as the background image and background clip text to make the effect.

I wanted to animate it so the spotlight follows the cursor so I added the handleMouseMove function which sets the useMotionValues. Then I use the values in the style to adjust the X and Y of the gradient.

Everything seems to be working, however I'm stuck on making it animate smoothly. For example, when the cursor is not hovering anymore I wanted to move the spotlight back to the middle. I made the handleMouseLeave function which does move it back to the middle, but it snaps it instead of animating it.

Some pics of the component (colors are changed)

enter image description here

In this second image I was hovering over the right end of the text, so the middle of the gradient (black) moved there

enter image description here

Code

const GradientBackgroundParagraph = ({ text }) => {
    let mouseX = useMotionValue(50);
    let mouseY = useMotionValue(50);

    const handleMouseMove = ({ clientX, clientY, currentTarget }) => {
        const { left, top, width, height } = currentTarget.getBoundingClientRect();

        mouseX.set(((clientX - left) / width) * 100);
        mouseY.set(((clientY - top) / height) * 100);
    };

    const handleMouseLeave = () => {
        mouseX.set(50);
        mouseY.set(50);
    };

    return (
        <motion.p
            onMouseMove={handleMouseMove}
            onMouseLeave={handleMouseLeave}
            style={{
                backgroundImage: useMotionTemplate`radial-gradient(circle at ${mouseX}% ${mouseY}%, rgb(var(--foreground-rgb)), red)`,
                backgroundClip: "text",
            }}
            className="text-2xl bg-clip-text text-transparent cursor-default transition-all"
        >
            {text}
        </motion.p>
    );
};

Update:

Here is a gif with the effect to get a better idea enter image description here


Solution

  • So for anyone that may have the same question in the future, I figured it out and it was quite simple. All I did was change useMotionValue to useSpring. Everything else was kept the same and the effect is now animated.

    Here is the final code: (Have done some cleanup, but the main part is the useSpring at the top)

    const GradientBackgroundParagraph = ({ text }) => {
        const mouseX = useMotionValue(50);
        const mouseY = useMotionValue(50);
        const mouseXSpring = useSpring(mouseX);
        const mouseYSpring = useSpring(mouseY);
    
        const handleMouseMove = ({ clientX, clientY, currentTarget }) => {
            const { left, top, width, height } = currentTarget.getBoundingClientRect();
    
            mouseX.set(((clientX - left) / width) * 100);
            mouseY.set(((clientY - top) / height) * 100);
        };
    
        const handleMouseLeave = () => {
            mouseX.set(50);
            mouseY.set(50);
        };
    
        const spotlightBackground = useMotionTemplate`radial-gradient(circle at ${mouseXSpring}% ${mouseYSpring}%, rgb(var(--foreground-rgb)), transparent 150%)`;
    
        return (
            <motion.p
                onMouseMove={handleMouseMove}
                onMouseLeave={handleMouseLeave}
                style={{
                    backgroundImage: spotlightBackground,
                    backgroundClip: "text",
                }}
                className="text-2xl bg-clip-text text-transparent cursor-default"
            >
                {text}
            </motion.p>
        );
    };