Home > front end >  Gradient Tracking Button with MUI
Gradient Tracking Button with MUI

Time:07-25

See the Codepen as example. I have a hover effect where the gradient follows the mouse cursor.

I have two CSS variables, --x and --y declated that are used to track the position of the mouse on the button. The CSS variable --size, is used to modify the gradient's dimensions.

With background: radial-gradient(circle closest-side, pink, transparent) it creates the gradient at the correct position.

The Document.querySelector() and EventTarget.addEventListener() register a handler for the 'mousemove' event.

And Element.getBoundingClientRect() and CSSStyleDeclaration.setProperty() updates the values of the --x and --y CSS variables.

Now I try to recreate it as styled component in React Typescript with MUI Button. I declared the CSS styles in my styled Button but it is not applied.

Button.tsx

import React from 'react';
import { styled, Theme } from '@mui/material/styles';
import { Button, SxProps } from '@mui/material';

const HoverButton = styled(Button)(({ theme }) => ({
    borderRadius: 100,
    ".mouse-cursor-gradient-tracking": {
        position: "relative",
        background: "#7983ff",
        padding: "0.5rem 1rem",
        fontSize: "1.2rem",
        border: "none",
        color: theme.palette.secondary.contrastText,
        cursor: "pointer",
        outline: "none",
        overflow: "hidden",
      },
      
      ".mouse-cursor-gradient-tracking span": {
        position: "relative",
      },
      
      ".mouse-cursor-gradient-tracking:before": {
        --size: 0,
        content: '',
        position: "absolute",
        left: "var(--x)",
        top: "var(--y)",
        width: "var(--size)",
        height: "var(--size)",
        background: "radial-gradient(circle closest-side, pink, transparent)",
        transform: "translate(-50%, -50%)",
        transition: "width 0.2s ease, height 0.2s ease",
      },
      
      ".mouse-cursor-gradient-tracking:hover:before": {
        "--size": "200px"
      },
}));

export function SubmitButton(props: { children: React.ReactNode; sx?: SxProps<Theme> }) {
    let button:<Element | null> = document.querySelector('.mouse-cursor-gradient-tracking');
    button.addEventListener('mousemove', e => {
        let rect = e.target.getBoundingClientRect();
        let x = e.clientX - rect.left;
        let y = e.clientY - rect.top;
        button.style.setProperty('--x', x   'px');
        button.style.setProperty('--y', y   'px');
    });

    return (
        <HoverButton type="submit" variant="contained" sx={props.sx}>
            {props.children}<span>Hover me</span>
        </HoverButton>
    );
}

CodePudding user response:

Here is a working codesandbox.

And let me explain a few shortcomings of your approach.

  1. Imperatively query selecting the button with a class and adding the event handler. Like below:
    let button:<Element | null> = document.querySelector('.mouse-cursor-gradient-tracking');
    button.addEventListener('mousemove', e => {
        let rect = e.target.getBoundingClientRect();
        let x = e.clientX - rect.left;
        let y = e.clientY - rect.top;
        button.style.setProperty('--x', x   'px');
        button.style.setProperty('--y', y   'px');
    });

You can instead add an event handler directly to a button component in React. So, React take care of how the event is added to the component.

const Component = () => {

  const mouseDownHandler = (e) => {
    // logic here
  }
  
 return (<button onm ouseDown={mouseDownHandler}>Click me</button>);
};
  1. Using classes mixed with styled or css-in-js.
const HoverButton = styled(Button)(({ theme }) => ({
    ".mouse-cursor-gradient-tracking": {
        position: "relative",
        background: "#7983ff",
      },
    ".mouse-cursor-gradient-tracking span": {
        position: "relative",
      },
      
      ".mouse-cursor-gradient-tracking:before": {
        --size: 0,
        content: '',
      },
      
      ".mouse-cursor-gradient-tracking:hover:before": {
        "--size": "200px"
      },
}));

Instead of this, you can simply do:

const HoverButton = styled(Button)`  
  position: relative;
  background: #7983ff;

  & > span {
    ... your code here
  }

  &:before {
   ... your code here
  }
  
  &:hover:before {
   ... your code here
  } 
`;

const SubmitButton = () => {
 return <HoverButton>Text</HoverButton>
}

The style will be directly applied to the button with the pseudo-classes. Your separate custom class (css-in-js engine will create a class with a hash on its own) is not needed until and unless you are trying to override an existing class from another library.

  1. Updating the styles dynamically.
button.addEventListener('mousemove', e => {
  ...other codes
  button.style.setProperty('--x', x   'px');
  button.style.setProperty('--y', y   'px');
});

This won't update the styles of the buttons dynamically. We can use a state which can be updated with the handler mentioned in point no. 1 and pass them as a prop to the styled button and use it to change the css values.

Here is the full working code.

import { useState } from "react";
import { styled, Theme } from "@mui/material/styles";
import { Button, SxProps } from "@mui/material";
import "./styles.css";

const StyledButton = styled(Button)<{ left: number; top: number }>`
  position: relative;
  background: #7983ff;
  padding: 0.5rem 1rem;
  font-size: 1.2rem;
  border: none;
  color: white;
  cursor: pointer;
  outline: none;
  overflow: hidden;

  &:before {
    content: "";
    left: ${({ left }) => left}px;
    top: ${({ top }) => top}px;
    position: absolute;
    width: 0;
    height: 0;
    background: radial-gradient(circle closest-side, pink, transparent);
    transform: translate(-50%, -50%);
    transition: width 0.2s ease, height 0.2s ease;
  }

  &:hover:before {
    color: red;
    width: 200px;
    height: 200px;
  }
`;

const SubmitButton = (props: {
  children?: React.ReactNode;
  sx?: SxProps<Theme>;
}) => {
  const [hoverStyle, setHoverStyle] = useState({
    left: 0,
    top: 0
  });

  const handleMouseMove: React.MouseEventHandler<HTMLButtonElement> = (e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const left = e.clientX - rect.left;
    const top = e.clientY - rect.top;

    setHoverStyle({ left, top });
  };

  return (
    <StyledButton
      left={hoverStyle.left}
      top={hoverStyle.top}
      type="submit"
      variant="contained"
      sx={props.sx}
      onm ouseMove={handleMouseMove}
    >
      <span>Hover me</span>
    </StyledButton>
  );
};

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <SubmitButton />
    </div>
  );
}

Links to the resources: MUI styled(), Styled component props

  • Related