How to make multiple selection rects?

I'm making a website with EJS (server with Express) which does a little bit of image processing. I want to make a feature where the user can select part of an image they upload and when a button to save is pressed the coordinates of the selected area are saved. They can do that as many times as needed. But if they don't click on the save button when they select an area again the old one will be overwritten. I have a problem when I select - the rectangles aren't at the correct coordinates and aren't working properly in general. I'm currently using HTML canvas to do that but any suggestions are welcome. Here's the code related:

Form (Note that it is EJS not HTML):

<form id="edit-form" action="/process" method="POST">
    <input
        type="text"
        id="comment-input"
        placeholder="Write a comment: "
        spellcheck="true"
    />

    <button onclick="addComment();" type="button">Add comment</button><br />

    <p id="feedback"></p>

    <ol id="comments-list"></ol>
    <br />

    <canvas id="edit-canvas"></canvas>
    <br />

    <input type="text" name="comments" style="display: none" />
    <input type="text" name="image" style="display: none" />

    <button type="submit">Submit</button>
</form>

<script src="./js/edit.js"></script>
<script>
    imageExport.value = "<%- image %>";

    const backgroundImage = new Image();

    backgroundImage.src = "/downloads/<%- image %>";

    backgroundImage.onload = () => {
        editCanvas.width = backgroundImage.width;
        editCanvas.height = backgroundImage.height;

        ctx.drawImage(backgroundImage, 0, 0);
    };
</script>

JavaScript code:

const editCanvas = document.querySelector("#edit-canvas");

const ctx = editCanvas.getContext("2d");

editCanvas.addEventListener("mousedown", (event) => {
    const startCoords = { x: event.offsetX, y: event.offsetY };

    // draw rect to mouse x and y
    ctx.fillRect(startCoords.x, startCoords.y, event.clientX, event.clientY);

    editCanvas.addEventListener("mouseup", (event2) => {
        ctx.clearRect(startCoords.x, startCoords.y, event.clientX, event.clientY);

        const endCoords = { x: event2.offsetX, y: event2.offsetY };

        ctx.fillRect(
            startCoords.x,
            startCoords.y,
            endCoords.x - startCoords.x,
            endCoords.y - startCoords.y
        );
    });
});

Solution 1:

There are a couple of mistakes in your code.

(1)

You're adding a mouseup listener inside the mousedown listener. That means everytime someone clicks on the canvas it creates a new listener, leading to the mouseup listener being fired multiple times.

(2)

You're just drawing the rectangle upon pressing/releasing the canvas but not while the user is actually moving the mouse.

(3)

Inside the callback functions you're using the event's .offsetX and .offsetY properties to access the position. That's wrong, you need to utilize .clientX and .clientY respectively. Furthermore the code doesn't take into account the on-screen position of the canvas element itself, which needs to be substracted from the mouse position.

If we go over all the points above we'll come up with this:

const editCanvas = document.querySelector("#edit-canvas");
const ctx = editCanvas.getContext("2d");
const backgroundImage = new Image();
let painting = false;

backgroundImage.onload = () => {
  editCanvas.width = backgroundImage.width;
  editCanvas.height = backgroundImage.height;

  ctx.drawImage(backgroundImage, 0, 0);
}

backgroundImage.src = "https://picsum.photos/id/237/200/300";
let startCoords, canvasPosition;

editCanvas.addEventListener("mousedown", (event) => {
  painting = true;
  canvasPosition = editCanvas.getBoundingClientRect();
  startCoords = {
    x: event.clientX - canvasPosition.left,
    y: event.clientY - canvasPosition.top
  };
});

editCanvas.addEventListener("mousemove", (event) => {
  if (painting) {
  canvasPosition = editCanvas.getBoundingClientRect();
    let endCoords = {
      x: event.clientX - canvasPosition.left,
      y: event.clientY - canvasPosition.top
    };
    ctx.drawImage(backgroundImage, 0, 0);
    ctx.fillStyle = 'rgba(255,0,0,0.4)';
    ctx.fillRect(
      startCoords.x,
      startCoords.y,
      endCoords.x - startCoords.x,
      endCoords.y - startCoords.y
    );
  }
});

editCanvas.addEventListener("mouseup", () => {
  painting = false;
});
<form id="edit-form" action="/process" method="POST">
  <input type="text" id="comment-input" placeholder="Write a comment: " spellcheck="true" />

  <button onclick="addComment();" type="button">Add comment</button><br />

  <p id="feedback"></p>

  <ol id="comments-list"></ol>
  <br />

  <canvas id="edit-canvas"></canvas>
  <br />

  <input type="text" name="comments" style="display: none" />
  <input type="text" name="image" style="display: none" />

  <button type="submit">Submit</button>
</form>