2 - Draggable Cards
2.1 Responding to Events

Draggable Note Cards

Now that our note cards are properly styled and placed in their rightful positions on screen, it's time to make them draggable.

Dynamic Card Position

To update a note position on the screen, we will need to change the position attribute from a static variable to use a react hook. Using the useState hook will allow the <NoteCard/> component to rerender each time we update the state, therefore allowing the position to be updated in our UI as well.

NoteCard.jsx
//let position = JSON.parse(note.position);
const [position, setPositon] = useState(JSON.parse(note.position));

To calculate the distance and direction moved, we will need to set the starting position from when the mouse is clicked, as well as a reference to the card itself so we can determine the final x & y position of the card.

NoteCard.jsx
let mouseStartPos = { x: 0, y: 0 };
 
const cardRef = useRef(null);

Set card ref: ref={cardRef} - This is the parent div in our NoteCard component.

Mouse Down Event

The first action taken to drag a note is the mousedown event, as a user clicks down on the header of our note card component.

On mouse down, we want to:

  1. Capture the starting x & y position of the mouse by referencing e.clientX & e.clientY.
  2. Add an event listener to the DOM that listens for the following mousemove events.

Note: We will create the mouseMove method in the next step.

const mouseDown = (e) => {
    mouseStartPos.x = e.clientX;
    mouseStartPos.y = e.clientY;
 
    document.addEventListener("mousemove", mouseMove);
};

Mouse move event

The mouse move event will capture EVERY movement of the users mouse, and will be responsible for updating our card position.

NoteCard.jsx
const mouseMove = (e) => {
    //1 - Calculate move direction
    let mouseMoveDir = {
        x: mouseStartPos.x - e.clientX,
        y: mouseStartPos.y - e.clientY,
    };
 
    //2 - Update start position for next move.
    mouseStartPos.x = e.clientX;
    mouseStartPos.y = e.clientY;
 
    //3 - Update card top and left position.
    setPositon({
        x: cardRef.current.offsetLeft - mouseMoveDir.x,
        y: cardRef.current.offsetTop - mouseMoveDir.y,
    });
};

The following will occur each time this method is called:

  1. We will capture the direction of the move by subtracting the starting position (set on mouseDown or in previous mouseMove) by the current position of the mouse. This will give us a result that looks something like this: {x:0, y:-1}. This states that the mouse moved 1 pixel up and zero pixels to the left.
  2. Once the direction has been calculated, we reset mouseStartPos to the current position, in preperation for the next move.
  3. Last, we set the official Note Card position by subtracting the cards offset Left & Top position by the move direction, therefore incrementally updating the cards position as we move it.

To test this out, let's add the mousedown event to our NoteCard header.

NoteCard.jsx
<div className="card-header" 
        //....
        onMouseDown = { mouseDown };
        >

Mouse up

If you are building along and testing this out right away, you may have noticed that the card stays stuck to our mouse even after we try to release the Note card. Let's resolve this now.

This can be solved with the mouseup event, which will simply remove the mousemove & mouseup from the DOM when we release the card.

NoteCard.jsx
const mouseUp = () => {
    document.removeEventListener("mousemove", mouseMove);
    document.removeEventListener("mouseup", mouseUp);
};

To use this, we must add the mouseup event listener in our original mousedown method.

const mouseDown = (e) => {
    mouseStartPos.x = e.clientX;
    mouseStartPos.y = e.clientY;
 
    document.addEventListener("mousemove", mouseMove);
    document.addEventListener("mouseup", mouseUp);
};