Detect if user trying resize a draggable element

Viewed 988

I'm trying create draggable elements that can also be resized by dragging bottom right corner with the help of resize: both CSS rule. So far I'm able drag it around screen, but I can't figure out how to detect if cursor at "resize" state, because attempting resize the element moves it instead.

Currently as a work around I'm simply checking if cursor within 20px from bottom-right corner, but that's not very accurate (and probably browser/os dependent), in some places it refuses resize and move the element at all.

Any suggestions?

!function(){
"use strict";
let x, y, drag;
document.addEventListener("mousedown", function(e) {
  if (e.target.parentNode.lastChild !== e.target && e.target.parentNode.classList.contains("main")) {
    //bring element to the front and dispatch mousedown event again otherwise resize doesn't work
    e.target.parentNode.appendChild(e.target);
    return e.target.dispatchEvent(new MouseEvent(e.type, e));
  }

  if (!e.target.classList.contains("draggable"))
    return;

  /* if cursor within 20px from bottom right corner don't move */
  const r = e.target.getBoundingClientRect();
  if (r.right < e.x + 20 && r.bottom < e.y + 20)
    return;

  drag = e.target;
  x = e.x - drag.offsetLeft;
  y = e.y - drag.offsetTop;
  document.body.classList.add("drag");
  drag.classList.add("drag");
});

document.addEventListener("mouseup", function(e) {
  document.body.classList.remove("drag");
  drag = drag && drag.classList.remove("drag");
});

document.addEventListener("mousemove", function(e) {
  if (!drag || e.x - drag.offsetLeft == x || e.y - drag.offsetTop == y)
    return;

  drag.style.left = (e.x - x) + "px";
  drag.style.top = (e.y - y) + "px";
});

/*init*/
for (let i = 0, c, d = document.getElementsByClassName("main")[0].children; i < d.length; i++) {
  c = (0x1000000 + Math.random() * 0xffffff).toString(16).substr(1, 6);
  d[i].style.backgroundColor = '#' + c;
  d[i].classList.toggle("dark", ((parseInt(c.substr(0, 2), 16) * 299) + (parseInt(c.substr(2, 2), 16) * 587) + (parseInt(c.substr(4, 2), 16) * 114)) / 1000 < 128);
  d[i].style.left = document.documentElement.scrollWidth / 8 + Math.random() * (document.documentElement.scrollWidth / 1.33 - d[i].offsetWidth) + "px";
  d[i].style.top = document.documentElement.scrollHeight / 8 + Math.random() * (document.documentElement.scrollHeight / 1.33 - d[i].offsetHeight) + "px";
}
}()
div.main>div {
  width: 5em;
  height: 5em;
  border: 1px solid black;
  position: absolute;
  resize: both;
  overflow: hidden;
  mix-blend-mode: hard-light;
  display: flex;
  border-radius: 0.3em;
}

div.main>div:hover {
  box-shadow: 0 0 5px black;
}

div.main>div:last-child {
  box-shadow: 0 0 10px black;
}

div.draggable {
  cursor: grab;
}

div.main>div:not(.draggable):before {
  content: "can't move me";
}

div.main>div.draggable:before {
  content: "move me";
}

div.main>div:before {
  color: black;
  text-shadow: 0 0 1em black;
  margin: auto;
  text-align: center;
}

div.main>div.dark:before {
  color: white;
  text-shadow: 0 0 1em white;
}

body.drag {
  user-select: none;
}

body.drag div.draggable {
  cursor: grabbing;
}
<div class="main">
  <div></div>
  <div class="draggable"></div>
  <div class="draggable"></div>
  <div class="draggable"></div>
</div>

1 Answers

You could wrap the mouse-target (.draggable) inside a resizeable container element.
This way, the UI for the CSS-resize will get hit before the draggable element and you can handle only the dragging nicely:

!function(){
"use strict";
let x, y, drag;
document.addEventListener("mousedown", function(e) {
  if (e.target.parentNode.lastChild !== e.target && e.target.parentNode.classList.contains("main")) {
    //bring element to the front and dispatch mousedown event again otherwise resize doesn't work
    e.target.parentNode.appendChild(e.target);
    return e.target.dispatchEvent(new MouseEvent(e.type, e));
  }

  if (!e.target.classList.contains("draggable"))
    return;

  e.preventDefault();
  drag = e.target.parentNode;
  x = e.x - drag.offsetLeft;
  y = e.y - drag.offsetTop;
  document.body.classList.add("drag");
  drag.classList.add("drag");
});

document.addEventListener("mouseup", function(e) {
  document.body.classList.remove("drag");
  drag = drag && drag.classList.remove("drag");
});

document.addEventListener("mousemove", function(e) {
  if (!drag || e.x - drag.offsetLeft == x || e.y - drag.offsetTop == y)
    return;

  drag.style.left = (e.x - x) + "px";
  drag.style.top = (e.y - y) + "px";
});

/*init*/
for (let i = 0, c, d = document.getElementsByClassName("main")[0].children; i < d.length; i++) {
  c = (0x1000000 + Math.random() * 0xffffff).toString(16).substr(1, 6);
  d[i].style.backgroundColor = '#' + c;
  d[i].classList.toggle("dark", ((parseInt(c.substr(0, 2), 16) * 299) + (parseInt(c.substr(2, 2), 16) * 587) + (parseInt(c.substr(4, 2), 16) * 114)) / 1000 < 128);
  d[i].style.left = document.documentElement.scrollWidth / 8 + Math.random() * (document.documentElement.scrollWidth / 1.33 - d[i].offsetWidth) + "px";
  d[i].style.top = document.documentElement.scrollHeight / 8 + Math.random() * (document.documentElement.scrollHeight / 1.33 - d[i].offsetHeight) + "px";
}
}()
.resizeable {
  width: 5em;
  height: 5em;
  border: 1px solid black;
  position: absolute;
  resize: both;
  overflow: hidden;
  mix-blend-mode: hard-light;
  border-radius: 0.3em;
}

div.main>div:hover {
  box-shadow: 0 0 5px black;
}

div.main>div:last-child {
  box-shadow: 0 0 10px black;
}

div.draggable {
  cursor: grab;
  width: 100%;
  height: 100%;
  display: flex;
}
div.no-drag {
  display: flex;
}
div.main div.no-drag:before {
  content: "can't move me";
}
div.draggable:before {
  content: "move me";
}
div.main div:before {
  color: black;
  text-shadow: 0 0 1em black;
  margin: auto;
  text-align: center;
}

div.main>div.dark:before {
  color: white;
  text-shadow: 0 0 1em white;
}

body.drag {
  user-select: none;
}

body.drag div.draggable {
  cursor: grabbing;
}
<div class="main">
  <div class="resizeable no-drag"></div>
  <div class="resizeable"><div class="draggable"></div></div>
  <div class="resizeable"><div class="draggable"></div></div>
  <div class="resizeable"><div class="draggable"></div></div>
</div>

Related