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

React中点击拖拽编辑数值的事件冲突问题求助

事件冲突解决与拖拽编辑实现方案

问题根源

  1. 每个Location组件都给window绑定了mousedown事件,导致点击页面时所有组件的回调都会触发,无法区分目标组件。
  2. 依赖window事件的offsetY属性判断点击区域,该值是相对于视口而非组件本身,坐标逻辑完全错误。
  3. e.stopPropagation()在window事件中无效,因为事件已经冒泡到全局层级。

解决方案

1. 将事件绑定到组件自身而非全局

直接在组件的根div上使用React的onMouseDown属性绑定事件,只有点击当前组件才会触发对应的回调,彻底避免全局监听的冲突。

修改组件返回部分:

return (
    <div 
        className="location" 
        ref={divRef}
        onMouseDown={handleMouseDown}
    >
        <div className="location-item">{props.bars}.</div>
        <div className="location-item">{props.beats}.</div>
        <div className="location-item">{props.divisions}</div>
    </div>
)

同时删除useEffect中全局的mousedown绑定:

useEffect(() => {
    console.log(`init`)
    // 移除全局mousedown绑定
    return () => {
        console.log(`shutdown`)
        window.removeEventListener('mouseup', handleMouseUp)
        window.removeEventListener('mousemove', handleMouseMove)
    }
}, [])

2. 修正坐标计算逻辑

组件自身的mousedown事件中,e.offsetY是相对于当前组件的,可直接用于判断点击区域。但mousemove事件绑定在window上,需要通过组件的位置计算相对坐标:

修改handleMouseMove函数:

const handleMouseMove = (e: any) => {
    if (!divRef.current) return;
    
    // 获取组件在视口中的位置
    const rect = divRef.current.getBoundingClientRect();
    // 计算鼠标相对于组件顶部的Y坐标
    const currentMouseY = e.clientY - rect.top;
    const distanceY = lastMouseYRef.current - currentMouseY;
    
    console.log(`location.handleMouseMove(${props.id}): drag distanceY ${distanceY}`)
    
    if (Math.abs(distanceY) > divRef.current.clientHeight) {
        console.log(`lastMouseYRef.current ${lastMouseYRef.current} - currentMouseY ${currentMouseY} > divRef.current.clientHeight ${divRef.current.clientHeight} `)
        const newLastMouseY = currentMouseY;
        setLastMouseY(newLastMouseY)
        lastMouseYRef.current = newLastMouseY;
        
        const offset: number = distanceY > 0 ? 1 : -1
        console.log(`bink!! distanceY ${distanceY} dragItemIndex ${dragItemIndexRef.current} offset ${offset}`)
        props.onEdit(dragItemIndexRef.current, offset)
    }
}

3. 补充禁用状态判断

在handleMouseDown开头添加enabled状态判断,禁用时不响应拖拽:

const handleMouseDown = (e: any) => {
    if (!props.enabled || !divRef.current) {
        return
    }
    // 原有逻辑...
}

完整修改后的组件代码

import React, {useEffect, useState, useRef} from "react"
import "./location.css"

export const Location = (props: any) => {

    const [mouseDown, setMouseDown] = useState<boolean>(false)
    const [lastMouseY, setLastMouseY] = useState<number>(0)
    const mouseDownRef = useRef(mouseDown)
    const lastMouseYRef = useRef(lastMouseY)
    const [dragItemIndex, setDragItemIndex] = useState<number>(0)
    const dragItemIndexRef = useRef(dragItemIndex)

    const divRef = useRef<HTMLDivElement>(null);

    const handleMouseDown = (e: any) => {
        if (!props.enabled || !divRef.current) {
            return
        }

        console.log(`location.handleMouseDown(${props.id}): e.offsetY ${e.offsetY}`)

        if (e.offsetY >= 0 && e.offsetY <= divRef.current.clientHeight) {
            console.log(`start ${props.id} e.offsetY ${e.offsetY}`)
            const initialMouseY = e.offsetY;
            setLastMouseY(initialMouseY)
            lastMouseYRef.current = initialMouseY;
            
            window.addEventListener('mouseup', handleMouseUp)
            window.addEventListener('mousemove', handleMouseMove)
            setMouseDown(true)

            let dragItemIndex = 1
            const clientLeft = divRef.current.getBoundingClientRect().left
            const offsetX = e.clientX - clientLeft
            if (offsetX < divRef.current.clientWidth / 3) {
                dragItemIndex = 0
            } else if (offsetX > (divRef.current.clientWidth * 2 / 3)) {
                dragItemIndex = 2
            }

            console.log(`mouse down - dragItemIndex ${dragItemIndex} e.offsetY ${e.offsetY}`)
            setDragItemIndex(dragItemIndex)
        }
    }

    const handleMouseUp = (e: any) => {
        setMouseDown(false)
        window.removeEventListener('mouseup', handleMouseUp)
        window.removeEventListener('mousemove', handleMouseMove)
    }

    const handleMouseMove = (e: any) => {
        if (!divRef.current) return;
        
        const rect = divRef.current.getBoundingClientRect();
        const currentMouseY = e.clientY - rect.top;
        const distanceY = lastMouseYRef.current - currentMouseY;
        
        console.log(`location.handleMouseMove(${props.id}): drag distanceY ${distanceY}`)
        
        if (Math.abs(distanceY) > divRef.current.clientHeight) {
            console.log(`lastMouseYRef.current ${lastMouseYRef.current} - currentMouseY ${currentMouseY} > divRef.current.clientHeight ${divRef.current.clientHeight} `)
            const newLastMouseY = currentMouseY;
            setLastMouseY(newLastMouseY)
            lastMouseYRef.current = newLastMouseY;
            
            const offset: number = distanceY > 0 ? 1 : -1
            console.log(`bink!! distanceY ${distanceY} dragItemIndex ${dragItemIndexRef.current} offset ${offset}`)
            props.onEdit(dragItemIndexRef.current, offset)
        }
    }

    useEffect(() => {
        mouseDownRef.current = mouseDown
    }, [mouseDown])

    useEffect(() => {
        lastMouseYRef.current = lastMouseY
    }, [lastMouseY])

    useEffect(() => {
        dragItemIndexRef.current = dragItemIndex
    }, [dragItemIndex])

    useEffect(() => {
        console.log(`init`)

        return () => {
            console.log(`shutdown`)
            window.removeEventListener('mouseup', handleMouseUp)
            window.removeEventListener('mousemove', handleMouseMove)
        }
    }, [])

    return (
        <div 
            className="location" 
            ref={divRef}
            onMouseDown={handleMouseDown}
        >
            <div className="location-item">{props.bars}.</div>
            <div className="location-item">{props.beats}.</div>
            <div className="location-item">{props.divisions}</div>
        </div>
    )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:17:13