skip to Main Content

I’m creating a custom cursor by dynamically appending a div element in the useEffect hook when the component mounts and is set to follow the mouse using event listeners. However, two divs are being rendered—one properly follows the mouse, while the other stays at the top-left corner of the viewport.

import { useRef, useEffect, useState } from "react"

function Pcanvas({width, height, pixelSize}){
    const [canvasColor, setCanvasColor] = useState("#dcdcdc")
    const [pencilSize, setPencilSize] = useState(pixelSize)
    const canvasRef = useRef(null)
    var mousePressed = false

    function mouseCursor(){
        const cursor = document.createElement("div")
        cursor.className = 'cursor'
        cursor.style.backgroundColor = 'black'
        cursor.style.width = pencilSize + 'px'
        cursor.style.height = pencilSize + 'px'
        cursor.style.position = 'absolute';
        cursor.style.display = 'block';
        cursor.style.zIndex = 999;
        cursor.style.pointerEvents = 'none';
        document.body.appendChild(cursor)
        
        console.log(cursor.getBoundingClientRect().y)
    }
    
    function handleMouseMov(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        console.log(cursor.getBoundingClientRect().y)
        if(cursor){
            cursor.style.top = event.clientY-(pencilSize/2) + 'px'
            cursor.style.left = event.clientX-(pencilSize/2) + 'px'
        }
    }

    function handleMouseLeave(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        if(cursor){
            cursor.style.display = 'none'
        }
    }
    
    function handleMouseEnter(event){
        const cursor = document.getElementsByClassName('cursor')[0]
        if(cursor){
            cursor.style.display = 'block'
        }
    }
    
    useEffect(() => {
        const canvas = canvasRef.current 
        mouseCursor()
        document.body.addEventListener('mouseenter', handleMouseEnter)
        document.body.addEventListener('mousemove', handleMouseMov)
        document.body.addEventListener('mouseleave', handleMouseLeave)

        return(() => {
            document.body.removeEventListener('mouseenter', handleMouseEnter)
            document.body.removeEventListener('mousemove', handleMouseMov)
            document.body.removeEventListener('mouseleave', handleMouseLeave)
        })
    }, [pencilSize])

    return(
        <></>
    )
}

export default Pcanvas

2

Answers


  1. You should avoid createElement and especially getElementsByClassName when using React.

    Let the component self render the div, then use an useRef to change any styling when needed.

    const { useRef, useEffect, useState } = React;
    
    function Pcanvas({ width, height, pixelSize = 15 }){
    
        const cursor = useRef(null)
        
        const [pencilSize, setPencilSize] = useState(pixelSize)
           
        const handleMouseEnter = (event) => cursor.current.style.display = 'block';
        const handleMouseLeave = (event) => cursor.current.style.display = 'none';
        const handleMouseMov = (event) => {
            cursor.current.style.top = event.clientY-(pencilSize/2) + 'px'
            cursor.current.style.left = event.clientX-(pencilSize/2) + 'px'
        }
        
        useEffect(() => {
            document.body.addEventListener('mouseenter', handleMouseEnter);
            document.body.addEventListener('mousemove', handleMouseMov);
            document.body.addEventListener('mouseleave', handleMouseLeave);
            return(() => {
                document.body.removeEventListener('mouseenter', handleMouseEnter);
                document.body.removeEventListener('mousemove', handleMouseMov);
                document.body.removeEventListener('mouseleave', handleMouseLeave);
            })
        }, [pencilSize])
        
        return (
            <div 
                ref={cursor} 
                className={'cursor'} 
                style={{ width: pencilSize, height: pencilSize, display: 'none' }}
            />
        );
    }
    
    ReactDOM.render(<Pcanvas />, document.getElementById("react"));
    html, body, #react { height: 100%; }
    
    .cursor {
      background: black;
      position: absolute;
      display: block;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
    Login or Signup to reply.
  2. To fix this, you should ensure that only one cursor is created. You can create the cursor once inside the useEffect hook when the component is mounted and remove it only when the component unmounts. Here’s an updated version of your code:

    import { useRef, useEffect, useState } from "react";
    
    function Pcanvas({ width, height, pixelSize }) {
      const [canvasColor, setCanvasColor] = useState("#dcdcdc");
      const [pencilSize, setPencilSize] = useState(pixelSize);
      const canvasRef = useRef(null);
    
      useEffect(() => {
        // Create cursor once
        const cursor = document.createElement("div");
        cursor.className = "cursor";
        cursor.style.backgroundColor = "black";
        cursor.style.width = pencilSize + "px";
        cursor.style.height = pencilSize + "px";
        cursor.style.position = "absolute";
        cursor.style.display = "none"; // Hide initially
        cursor.style.zIndex = 999;
        cursor.style.pointerEvents = "none";
        document.body.appendChild(cursor);
    
        // Mouse move handler
        function handleMouseMov(event) {
          cursor.style.top = event.clientY - pencilSize / 2 + "px";
          cursor.style.left = event.clientX - pencilSize / 2 + "px";
        }
    
        // Mouse enter and leave handlers
        function handleMouseEnter() {
          cursor.style.display = "block"; // Show cursor on mouse enter
        }
    
        function handleMouseLeave() {
          cursor.style.display = "none"; // Hide cursor on mouse leave
        }
    
        // Attach event listeners
        document.body.addEventListener("mouseenter", handleMouseEnter);
        document.body.addEventListener("mousemove", handleMouseMov);
        document.body.addEventListener("mouseleave", handleMouseLeave);
    
        // Cleanup
        return () => {
          document.body.removeChild(cursor); // Remove cursor when component unmounts
          document.body.removeEventListener("mouseenter", handleMouseEnter);
          document.body.removeEventListener("mousemove", handleMouseMov);
          document.body.removeEventListener("mouseleave", handleMouseLeave);
        };
      }, [pencilSize]);
    
      return <></>;
    }
    
    export default Pcanvas;
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search