Home > Net >  How can I track direction of mouse move only during mousedown?
How can I track direction of mouse move only during mousedown?

Time:10-07

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)

  1. enabling and disabling the tracking of the mouse event on mousedown and mouseup respectively
  2. 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>

  • Related