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.
- 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>);
};
- Using classes mixed with
styled
orcss-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.
- 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