0

Here below is how to draw a "selection rectangle" on a <canvas> with drag-and-drop, see How to draw a selection rectangle with drag and drop on a HTML canvas?.

Is there a simple way to detect the selection rectangle on hover at a distance of a few pixels, and allow to move the selection rectangle with drag-and-drop?

var c1 = document.getElementById("c1"), c2 = document.getElementById("c2");
var ctx1 = c1.getContext("2d"), ctx2 = c2.getContext("2d");
ctx2.setLineDash([5, 5]);
var origin = null;
window.onload = () => { ctx1.drawImage(document.getElementById("img"), 0, 0); }
c2.onmousedown = e => { origin = {x: e.offsetX, y: e.offsetY}; };
window.onmouseup = e => { origin = null; };
c2.onmousemove = e => { 
    if (!!origin) { 
        ctx2.strokeStyle = "#ff0000";
        ctx2.clearRect(0, 0, c2.width, c2.height);
        ctx2.beginPath();
        ctx2.rect(origin.x, origin.y, e.offsetX - origin.x, e.offsetY - origin.y); 
        ctx2.stroke(); 
    } 
};
#img { display: none; }
#canvas-container { position: relative; }
canvas { position: absolute; left: 0; top: 0; }
#c1 { z-index: 0; }
#c2 { z-index: 1; }
<img id="img" src="https://i.imgur.com/okSIKkW.jpg">
<div id="canvas-container">
<canvas id="c1" height="200" width="200"></canvas>
<canvas id="c2" height="200" width="200"></canvas>
</div>
Basj
  • 41,386
  • 99
  • 383
  • 673
  • This https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath can give you some ideas. – DraganS Oct 24 '22 at 13:09

1 Answers1

2

Storing the current selection

To be able to move an existing selection, you have to save its state. Right now, your code "forgets" about it after drawing it once.

You can save your selection in a variable on mouseup like so:

const dx = origin.x - mouse.x;
const dy = origin.y - mouse.y;

selection = {
  left:   Math.min(mouse.x, origin.x),  
  top:    Math.min(mouse.y, origin.y),
  width:  Math.abs(dx),
  height: Math.abs(dy)
}

Intersection check

Your selection is a rectangle. You can check whether the mouse is intersecting the rectangle like so:

const intersects = (
  mouse.x >= selection.left &&
  mouse.x <= selection.left + selection.width &&
  mouse.y >= selection.top &&
  mouse.y <= selection.top + selection.height
);

If you want to add some padding around the rectangle, you can change the checks to be, for example, mouse.x >= selection.left - PADDING.

Make or Move

You now have to support 2 types of interactions:

  1. Making new selections, and
  2. Moving existing selections

This is where my implementation gets a bit messy, but you can probably refactor it yourself

I didn't change much in the making-selections code, other than saving the selection to a variable.

When moving selections, you take the dx and dy of your mouse drag and add them to the selection's original position (ox, oy):

const dx = origin.x - mouse.x;
const dy = origin.y - mouse.y;

selection.left = ox - dx;
selection.top = oy - dy;

Here's everything in a runnable snippet:

var c1 = document.getElementById("c1"),
    c2 = document.getElementById("c2");
var ctx1 = c1.getContext("2d"),
    ctx2 = c2.getContext("2d");
  
ctx2.setLineDash([5, 5]);

var origin = null;
let selection = null;
let selectionHovered = false;
let interaction = null;
let ox = null;
let oy = null;

window.onload = () => { ctx1.drawImage(document.getElementById("img"), 0, 0); }

c2.onmousedown = e => { 
  origin = {x: e.offsetX, y: e.offsetY};
  
  if (selectionHovered) {
    interaction = "MOVE_SELECTION";
    ox = selection.left;
    oy = selection.top;
  } else {
    interaction = "MAKE_SELECTION";
  }
  
};

window.onmouseup = e => {
  interaction = null;
  origin = null;
};

c2.onmousemove = e => {
  const x = e.offsetX;
  const y = e.offsetY;
  
  if (!interaction) {
   selectionHovered = (
      selection &&
      x >= selection.left &&
      x <= selection.left + selection.width &&
      y >= selection.top &&
      y <= selection.top + selection.height
    );
  } else {
    const dx = origin.x - x;
    const dy = origin.y - y;

    // Update
    switch (interaction) {
      case "MOVE_SELECTION":
        selection.left = ox - dx;
        selection.top = oy - dy;

        break;
      case "MAKE_SELECTION":
        selection = {
          left:   Math.min(x, origin.x),
          top:    Math.min(y, origin.y),
          width:  Math.abs(dx),
          height: Math.abs(dy)
        }
        break
      default:
        // Do nothing
    }


    // Set selectionHovered
    if (selection) {

    } else {
      selectionHovered = false;
    }
  }

  // Draw
  if (selection) drawSelection(selection);
};

function drawSelection({ top, left, width, height }) {
  // Draw rect
  ctx2.strokeStyle = "#ff0000";
  ctx2.clearRect(0, 0, c2.width, c2.height);
  ctx2.beginPath();
  ctx2.rect(left, top, width, height); 
  ctx2.stroke(); 
  
  // Set mouse 
  c2.style.cursor = selectionHovered ? "move" : "default";
  
}
#img { display: none; }
body { margin: 0; }
#canvas-container { position: relative; }
canvas { position: absolute; left: 0; top: 0; }
#c1 { z-index: 0; }
#c2 { z-index: 1; }
<img id="img" src="https://i.imgur.com/okSIKkW.jpg">
<div id="canvas-container">
  <canvas id="c1" height="200" width="200"></canvas>
  <canvas id="c2" height="200" width="200"></canvas>
</div>
user3297291
  • 22,592
  • 4
  • 29
  • 45