如何在 React 中处理父级的 mouseMove 事件?
Posted
技术标签:
【中文标题】如何在 React 中处理父级的 mouseMove 事件?【英文标题】:How to process mouseMove event in parent in React? 【发布时间】:2019-11-11 08:35:46 【问题描述】:我正在尝试在 React 中实现拖放并使用 SVG 元素。问题是如果用户移动鼠标过快,mouseMove
不会被触发。它基本上经常失去拖动。为了解决这个问题,我认为我需要处理父级中的mouseMove
,但不确定如何使用 React 来执行此操作。我尝试了几种不同的方法都无济于事。
我使用 ref 在父级上尝试了addEventListener('mousemove', ...)
,但问题是clientX
与当前组件的坐标系不同。此外,事件处理程序无权访问组件的任何状态(带有箭头函数的事件)。它维护对任何状态的过时引用。
我尝试在父级的context
中设置clientX
和clientY
,然后将其从DragMe
组件中拉入,但由于某种奇怪的原因,第一次总是undefined
我给它一个默认值。
这是我正在使用的代码:
const DragMe = ( x = 50, y = 50, r = 10 ) =>
const [dragging, setDragging] = useState(false)
const [coord, setCoord] = useState( x, y )
const [offset, setOffset] = useState( x: 0, y: 0 )
const [origin, setOrigin] = useState( x: 0, y: 0 )
const xPos = coord.x + offset.x
const yPos = coord.y + offset.y
const transform = `translate($xPos, $yPos)`
const fill = dragging ? 'red' : 'green'
const stroke = 'black'
const handleMouseDown = e =>
setDragging(true)
setOrigin( x: e.clientX, y: e.clientY )
const handleMouseMove = e =>
if (!dragging) return
setOffset(
x: e.clientX - origin.x,
y: e.clientY - origin.y,
)
const handleMouseUp = e =>
setDragging(false)
setCoord( x: xPos, y: yPos )
setOrigin( x: 0, y: 0 )
setOffset( x: 0, y: 0 )
return (
<svg style= userSelect: 'none'
onMouseDown=handleMouseDown
onMouseUp=handleMouseUp
onMouseMove=handleMouseMove
onMouseLeave=handleMouseUp
>
<circle transform=transform cx="0" cy="0" r=r fill=fill stroke=stroke />
</svg>
)
【问题讨论】:
尝试添加 e.preventDefault();在每个事件方法中 也尝试添加e.stopPropagation()
我最初尝试过,但问题不是某处的其他事件。问题是 mousemove
仅在鼠标仍在对象上方时才会发出。当鼠标移动过快时,光标将在其位置更新之前离开对象。
【参考方案1】:
经过大量实验,我能够addEventListener
到父画布。我发现我需要useRef
才能让mousemove
处理程序看到当前状态。我之前遇到的问题是 handleParentMouseMove
处理程序对状态的引用过时并且从未见过 startDragPos
。
这是我想出的解决方案。如果有人知道清理此问题的方法,将不胜感激。
const DragMe = ( x = 50, y = 50, r = 10, stroke = 'black' ) =>
// `mousemove` will not generate events if the user moves the mouse too fast
// because the `mousemove` only gets sent when the mouse is still over the object.
// To work around this issue, we `addEventListener` to the parent canvas.
const canvasRef = useContext(CanvasContext)
const [dragging, setDragging] = useState(false)
// Original position independent of any dragging. Updated when done dragging.
const [originalCoord, setOriginalCoord] = useState( x, y )
// The distance the mouse has moved since `mousedown`.
const [delta, setDelta] = useState( x: 0, y: 0 )
// Store startDragPos in a `ref` so handlers always have the latest value.
const startDragPos = useRef( x: 0, y: 0 )
// The current object position is the original starting position + the distance
// the mouse has moved since the start of the drag.
const xPos = originalCoord.x + delta.x
const yPos = originalCoord.y + delta.y
const transform = `translate($xPos, $yPos)`
// `useCallback` is needed because `removeEventListener`` requires the handler
// to be the same as `addEventListener`. Without `useCallback` React will
// create a new handler each render.
const handleParentMouseMove = useCallback(e =>
setDelta(
x: e.clientX - startDragPos.current.x,
y: e.clientY - startDragPos.current.y,
)
, [])
const handleMouseDown = e =>
setDragging(true)
startDragPos.current = x: e.clientX, y: e.clientY
canvasRef.current.addEventListener('mousemove', handleParentMouseMove)
const handleMouseUp = e =>
setDragging(false)
setOriginalCoord( x: xPos, y: yPos )
startDragPos.current = x: 0, y: 0
setDelta( x: 0, y: 0 )
canvasRef.current.removeEventListener('mousemove', handleParentMouseMove)
const fill = dragging ? 'red' : 'green'
return (
<svg style= userSelect: 'none'
onMouseDown=handleMouseDown
onMouseUp=handleMouseUp
>
<circle transform=transform cx="0" cy="0" r=r fill=fill stroke=stroke />
</svg>
)
【讨论】:
【参考方案2】:只是为了隔离在这里工作的代码:
首先在render()
函数中弄清楚你的***父级是什么,你可能有这样的东西:
render()
return (
<div ref=(parent_div) => this.parent_div = parent_div >
// other div stuff
</div>
)
然后,使用上面的 ref
分配,继续为它分配事件侦听器,如下所示:
this.parent_div.addEventListener('mousemove', function (event)
console.log(event.clientX)
【讨论】:
以上是关于如何在 React 中处理父级的 mouseMove 事件?的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用react中的useEffect挂钩从子级设置父级的状态