Gradient change of border dont work on mouse move

Viewed 123

this Codepen uses conic-gradient to create the border. When you move your mouse over the element you see the gradient change. JavaScript is updating a CSS custom property that stores the rotation angle. The border-image-source property sets the source image used to create an element's border. As with other properties that accept an image value, any CSS gradient type is valid too.

Now when I try to recreate this with styled components in React TS I get the gradient border. But it does not change when I move with the mouse, like in the Codepen?

My App.tsx

import React from "react";
import styled from "styled-components";

export interface Props {
  card: Element;
}

const Card = styled.div`
  --startDeg: 0deg;
  inline-size: 50vmin;
  block-size: 50vmin;

  border: 5vmin solid hsl(100 100% 60%);
  border-image-slice: 1;

  border-image-source: conic-gradient(
    from var(--startDeg, 0deg),
    hsl(100 100% 60%),
    hsl(200 100% 60%),
    hsl(100 100% 60%)
  );

  display: grid;
  place-content: center;
  padding: 4ch;
  box-sizing: border-box;
  font-size: 10vmin;
`;

function App() {
  const card: Element = document.querySelector(".card");
  window.addEventListener(
    "mousemove",
    ({ clientX, clientY }) => {
      const { x, y, width, height } = card.getBoundingClientRect();
      const dx = clientX - (x + 0.5 * width);
      const dy = clientY - (y + 0.5 * height);
      const angle = (Math.atan2(dy, dx) * 180) / Math.PI;

      card.style.setProperty("--startDeg", `${angle + 90}deg`);
    },
    false
  );

  return <Card>Conic Gradient Border</Card>;
}

export default App;


1 Answers

In this codesandbox example you can see the working version. (JS version)

Code:

import React, { useEffect, useRef, useCallback } from "react";
import styled from "styled-components";

const Card = styled.div`
  --startDeg: 0deg;
  inline-size: 50vmin;
  block-size: 50vmin;

  border: 5vmin solid hsl(100 100% 60%);
  border-image-slice: 1;

  border-image-source: conic-gradient(
    from var(--startDeg, 0deg),
    hsl(100 100% 60%),
    hsl(200 100% 60%),
    hsl(100 100% 60%)
  );

  display: grid;
  place-content: center;
  padding: 4ch;
  box-sizing: border-box;
  font-size: 10vmin;
`;

function App() {
  const cardRef = useRef();

  const mouseMoveListener = useCallback(({ clientX, clientY }) => {
    const card = cardRef.current;
    const { x, y, width, height } = card.getBoundingClientRect();
    const dx = clientX - (x + 0.5 * width);
    const dy = clientY - (y + 0.5 * height);
    const angle = (Math.atan2(dy, dx) * 180) / Math.PI;

    card.style.setProperty("--startDeg", `${angle + 90}deg`);
  }, []);

  useEffect(() => {
    window.addEventListener("mousemove", mouseMoveListener, false);
    return () => window.removeEventListener("mousemove", mouseMoveListener);
  }, [mouseMoveListener]);

  return <Card ref={cardRef}>Conic Gradient Border</Card>;
}

export default App;

Explanation:

Based on React lifecycle, querySelector doesn't work properly due to the fact that React component doesn't finish painting the DOM when the component is mounted. Therefore, using a ref will ensure that the html element will be targeted correctly.

Also, it is a good practice to register listeners inside the useEffect on the initial mount and remove it with a cleanup function to avoid memory leaks.

Related