You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React自定义光标渲染两个div:一个随动一个固定左上角

解决React自定义光标出现重复元素的问题

你的问题核心在于未清理旧DOM元素 + useEffect依赖触发重复执行:

  • 当pencilSize变化时,useEffect会重新执行,每次都会调用mouseCursor()创建新的.cursor div并添加到body,但之前的旧光标没被删除,导致页面积累多个光标元素。
  • 事件处理函数只操作第一个.cursor元素,新创建的光标没人控制,就会固定在视口左上角。

修复步骤

  1. 在useEffect清理阶段删除旧光标:每次useEffect重新执行前,移除之前创建的光标元素,避免重复创建。
  2. 用ref保存光标引用:不用getElementsByClassName查找元素,直接通过ref操作当前光标,避免取错对象。
  3. 用useCallback包裹事件函数:保证函数实例稳定,避免组件重渲染时频繁解绑/绑定事件。

修改后的完整代码

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

function Pcanvas({width, height, pixelSize}){
    const [canvasColor, setCanvasColor] = useState("#dcdcdc")
    const [pencilSize, setPencilSize] = useState(pixelSize)
    const canvasRef = useRef(null)
    const cursorRef = 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)
        cursorRef.current = cursor // 将光标存入ref
        return cursor
    }
    
    // 用useCallback包裹,依赖pencilSize确保尺寸变化时函数更新
    const handleMouseMov = useCallback((event) => {
        const cursor = cursorRef.current
        if(cursor){
            cursor.style.top = `${event.clientY - pencilSize/2}px`
            cursor.style.left = `${event.clientX - pencilSize/2}px`
        }
    }, [pencilSize])

    const handleMouseLeave = useCallback(() => {
        const cursor = cursorRef.current
        if(cursor){
            cursor.style.display = 'none'
        }
    }, [])
    
    const handleMouseEnter = useCallback(() => {
        const cursor = cursorRef.current
        if(cursor){
            cursor.style.display = 'block'
        }
    }, [])
    
    useEffect(() => {
        const cursor = 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)
            if(cursor) {
                document.body.removeChild(cursor)
                cursorRef.current = null
            }
        })
    }, [pencilSize, handleMouseEnter, handleMouseMov, handleMouseLeave]) // 加入事件函数依赖确保监听正确

    return(
        <></>
    )
}

export default Pcanvas

内容的提问来源于stack exchange,提问作者Ahsan914

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:31:07