theHacker
theHacker

Reputation: 4043

Problems implementing dragging by mouse

I want to implement a draggable map containing certain elements.

--> See JSFiddle: https://jsfiddle.net/7ndx7s25/7/

By use of mousedown, mousemove and mouseup I achieved the dragging. However I am facing problems:

  1. When pressing the mouse button down and then moving outside the window I do not get a mouseup event. Reentering the window (having released the mouse button long ago) my map still thinks the button is down and misbehaves accordingly.

  2. When there are objects on the map, I do not get mousemove events while moving through these objects. Therefore the map hangs and jumps as I enter and leave such an object.

  3. While over such objects I still want to have a move mouse cursor. I could change the cursor style on each object (in the Fiddle I did this for Object 1 as an example), but this doesn't seem like a good way. Is there a more elegant solution?

Upvotes: 0

Views: 49

Answers (1)

Asons
Asons

Reputation: 87191

You need e.g. mouseout to catch when leaving the canvas, though that event will also fire when the cursor move over the other elements.

One easy fix is to simply add a class to canvas, that set pointer-events: none on those.

With that class you can control the cursor as well, and avoid setting it with the script.

Stack snippet

updateInfo = function() {
  document.getElementById('info').innerHTML =
    'Position = ' + JSON.stringify(position) +
    '<br />dragInfo = ' + JSON.stringify(dragInfo);
};

const canvas = document.getElementsByTagName('canvas')[0];

let position = { x: 0, y : 0 };
let dragInfo = null;
updateInfo();

canvas.addEventListener('mousedown', function(e) {  
  dragInfo = {
    startEvent: {
      x: e.clientX,
      y: e.clientY,
    },
    startPosition: position
  };
  canvas.classList.add('dragging');
  updateInfo();
});

canvas.addEventListener('mousemove', function(e) {  
  if (dragInfo === null) return;
  
  position = {
    x: dragInfo.startPosition.x - (e.clientX - dragInfo.startEvent.x),
    y: dragInfo.startPosition.y - (e.clientY - dragInfo.startEvent.y)
  };
  updateInfo();
});

canvas.addEventListener('mouseup', function(e) {  
  dragInfo = null;
  canvas.classList.remove('dragging');
  updateInfo();
});

canvas.addEventListener('mouseout', function(e) {
  dragInfo = null;
  canvas.classList.remove('dragging');
  updateInfo();
});
* {
  user-select: none;
  font-family: monospace;
}
canvas {
  background: black;
  border: 1px solid red;
}
.dragging {
  cursor: move;
}
.obj {
  position: absolute;
  width: 50px;
  height: 50px;
  background: green;
  color: white;
  text-align: center;
  line-height: 50px;
  font-weight: bold;
}
.dragging ~ .obj {
  pointer-events: none;
}
<div id="myMap-ish">
  <canvas width="500" height="300"></canvas>
  <div class="obj" style="left: 30px; top: 35px">1</div>
  <div class="obj" style="left: 175px; top: 79px">2</div>
  <div class="obj" style="left: 214px; top: 145px">3</div>
  <div class="obj" style="left: 314px; top: 215px">4</div>
</div>
<div id="info"></div>


Another option could be to use mouseleave, on the outer wrapper, the myMap-ish element, which could be combined with the above added class to simply cursor handling.

The main difference between mouseout and mouseleave is that the latter won't fire when hovering children, as shown in below sample, so we don't need to toggle pointer-events as we did in the first sample.

Note, to simply use mouseleave in the first sample, on canvas, will have the same issue mouseout has, since the "other element" aren't children of the canvas.

Stack snippet

updateInfo = function() {
  document.getElementById('info').innerHTML =
    'Position = ' + JSON.stringify(position) +
    '<br />dragInfo = ' + JSON.stringify(dragInfo);
};

const canvas = document.getElementById('myMap-ish');

let position = { x: 0, y : 0 };
let dragInfo = null;
updateInfo();

canvas.addEventListener('mousedown', function(e) {  
  dragInfo = {
    startEvent: {
      x: e.clientX,
      y: e.clientY,
    },
    startPosition: position
  };
  canvas.style.cursor = 'move';
  document.querySelectorAll('.obj')[0].style.cursor = 'move'; // TODO for all objects
  updateInfo();
});

canvas.addEventListener('mousemove', function(e) {  
  if (dragInfo === null) return;
  
  position = {
    x: dragInfo.startPosition.x - (e.clientX - dragInfo.startEvent.x),
    y: dragInfo.startPosition.y - (e.clientY - dragInfo.startEvent.y)
  };
  updateInfo();
});

canvas.addEventListener('mouseup', function(e) {  
  dragInfo = null;
  canvas.style.cursor = 'default';
  document.querySelectorAll('.obj')[0].style.cursor = 'default';  // TODO for all objects
  updateInfo();
});

canvas.addEventListener('mouseleave', function(e) {  
  dragInfo = null;
  canvas.style.cursor = 'default';
  document.querySelectorAll('.obj')[0].style.cursor = 'default';  // TODO for all objects
  updateInfo();
});
* {
  user-select: none;
  font-family: monospace;
}
canvas {
  background: black;
  border: 1px solid red;
}
.obj {
  position: absolute;
  width: 50px;
  height: 50px;
  background: green;
  color: white;
  text-align: center;
  line-height: 50px;
  font-weight: bold;
}
<div id="myMap-ish">
  <canvas width="500" height="300"></canvas>
  <div class="obj" style="left: 30px; top: 35px">1</div>
  <div class="obj" style="left: 175px; top: 79px">2</div>
  <div class="obj" style="left: 214px; top: 145px">3</div>
  <div class="obj" style="left: 314px; top: 215px">4</div>
</div>
<div id="info"></div>

Upvotes: 1

Related Questions