I am trying to track the direction in which the mouse is moving, but only during mousedown
. As a result, I either have to choose between mousemove
or mousedown
.
I wrote a small hook and have been doing it with a mousemove
event listener, but I would like to optimise things a bit and only call it when needed, plus I would like the state to be idle when not moving.
I tried to write a useEffect()
and add the event listener inside the first event listener for mousedown
and it works, but after a while the app gets very slow, since it rerenders constantly.
Is there something I am missing? I tried to turn the function into a callback with useCallback()
, but this is not really working inside useEffect()
. Thanks for any help.
CodePudding user response:
It seems like you need to have two separate triggers (effects)
- enabling and disabling the tracking of the mouse event on
mousedown
andmouseup
respectively - when tracking is enabled track the
mousemove
event and update the position / direction of the movement
I made a crude version of what you are looking for:
see a working demo
import { useCallback, useEffect, useRef, useState } from "react";
export const useMouse = () => {
const [tracking, setTracking] = useState(false);
const [direction, setDirection] = useState("");
const prevPos = useRef();
const updatePosition = useCallback(({ x, y }) => {
prevPos.current = { x, y };
}, []);
const move = useCallback(
(event) => {
if (!prevPos.current) {
prevPos.current = {
x: event.x,
y: event.y
};
return;
}
const threshold = 50;
const current = prevPos.current;
if (event.x < current.x - threshold) {
setDirection("left");
updatePosition(event);
} else if (event.x > current.x threshold) {
setDirection("right");
updatePosition(event);
} else if (event.y < current.y - threshold) {
setDirection("up");
updatePosition(event);
} else if (event.y > current.y threshold) {
setDirection("down");
updatePosition(event);
}
},
[updatePosition]
);
const enableTracking = useCallback(() => {
setTracking(true);
}, []);
const disableTracking = useCallback(() => {
setTracking(false);
}, []);
useEffect(() => {
if (!tracking) return;
document.addEventListener("mousemove", move);
return () => {
document.removeEventListener("mousemove", move);
};
}, [tracking, move]);
useEffect(() => {
document.addEventListener("mousedown", enableTracking);
document.addEventListener("mouseup", disableTracking);
return () => {
document.removeEventListener("mousedown", enableTracking);
document.removeEventListener("mouseup", disableTracking);
};
}, [enableTracking, disableTracking, setTracking]);
return {
tracking,
direction
};
};
CodePudding user response:
A simple solution using pointer events
This solution uses the newer pointer events rather than mouse events. The result is the same, but as MDN says:
Much of today's web content assumes the user's pointing device will be a mouse. However, since many devices support other types of pointing input devices, such as pen/stylus and touch surfaces, extensions to the existing pointing device event models are needed. Pointer events address that need.
The pointerdown event adds a pointermove event handler. The pointer is captured to ensure movement is tracked even outside the box. And when the pointer is release it removes the pointermove handler and the capture is released implicitly.
Run the snippet to try
box.addEventListener("pointerdown", function(e) {
this.setPointerCapture(e.pointerId);
this.addEventListener("pointermove", handler);
function handler(e) {
if (!e.buttons) {
e.target.removeEventListener("pointermove", handler);
}
stdout.innerHTML = `${e.clientX.toFixed(0)}, ${e.clientY.toFixed(0)}`;
}
});
body {
padding: 1em;
background-color: whitesmoke;
}
#box {
height: 8em;
background-color: white;
border: 1px solid red;
border-radius: 5px;
}
<div id="stdout">Click and move the mouse in the box</div>
<div id="box"></div>