Quantcast
Channel: Active questions tagged html - Stack Overflow
Viewing all articles
Browse latest Browse all 72358

Prevent click event if dragged

$
0
0

I asked a related question here

I'm trying to stop click events if dragged.

I think the simplest version of this is dragging yourself. Basically IF the user presses down, then moves, then releases I don't want a click event.

Note, the code below is not trying to allow click, it's trying only to prevent it. I thought calling preventDefault in mouseup would tell the browser, don't do the default thing, that being sending a click event because the user let up on the mouse.

let dragTarget;
let dragMouseStartX;
let dragMouseStartY;
let dragTargetStartX;
let dragTargetStartY;

const px = v => `${v}px`;

function dragStart(e) {
  e.preventDefault();
  e.stopPropagation();
  dragTarget = this;
  const rect = this.getBoundingClientRect();
  dragMouseStartX = e.pageX;
  dragMouseStartY = e.pageY;
  dragTargetStartX = (window.scrollX + rect.left) | 0; 
  dragTargetStartY = (window.scrollY + rect.top) | 0; 

  window.addEventListener('mousemove', dragMove, {passive: false});
  window.addEventListener('mouseup', dragStop, {passive: false});
}

function dragMove(e) {
  if (dragTarget) {
    e.preventDefault();
    e.stopPropagation();
    const x = dragTargetStartX + (e.pageX - dragMouseStartX);
    const y = dragTargetStartY + (e.pageY - dragMouseStartY);
    dragTarget.style.left = px(x);
    dragTarget.style.top = px(y);
  }
}

function dragStop(e) {
  e.preventDefault();
  e.stopPropagation();
  dragTarget = undefined;
  window.removeEventListener('mousemove', dragMove);
  window.removeEventListener('mouseup', dragStop);
}

document.querySelector('.drag').addEventListener('mousedown', dragStart);
document.querySelector('.drag').addEventListener('click', () => {
  console.log('clicked', new Date());
});
body { height: 100vh; }
.drag { 
  background: red;
  color: white;
  padding: 1em;
  position: absolute;
  user-select: none;
  cursor: pointer;
}
<div class="drag">drag and release me</div>

One solution is to remove the click event and just do it myself in mouseup. If there was no movement call whatever I was going to call for click

But, in my actual use case dragging is on the parent like this (you can drag red or blue)

let dragTarget;
let dragMouseStartX;
let dragMouseStartY;
let dragTargetStartX;
let dragTargetStartY;

const px = v => `${v}px`;

function dragStart(e) {
  e.preventDefault();
  e.stopPropagation();
  dragTarget = this;
  const rect = this.getBoundingClientRect();
  dragMouseStartX = e.pageX;
  dragMouseStartY = e.pageY;
  dragTargetStartX = (window.scrollX + rect.left) | 0; 
  dragTargetStartY = (window.scrollY + rect.top) | 0; 

  window.addEventListener('mousemove', dragMove, {passive: false});
  window.addEventListener('mouseup', dragStop, {passive: false});
}

function dragMove(e) {
  if (dragTarget) {
    e.preventDefault();
    e.stopPropagation();
    const x = dragTargetStartX + (e.pageX - dragMouseStartX);
    const y = dragTargetStartY + (e.pageY - dragMouseStartY);
    dragTarget.style.left = px(x);
    dragTarget.style.top = px(y);
  }
}

function dragStop(e) {
  e.preventDefault();
  e.stopPropagation();
  dragTarget = undefined;
  window.removeEventListener('mousemove', dragMove);
  window.removeEventListener('mouseup', dragStop);
}

document.querySelector('.drag').addEventListener('mousedown', dragStart);
document.querySelector('.click').addEventListener('click', () => {
  console.log('clicked', new Date());
});
body { height: 100vh; }
.drag { 
  background: blue;
  color: white;
  padding: 2em;
  position: absolute;
  user-select: none;
  cursor: pointer;
}
.click {
  padding: 1em;
  background: red;
}
<div class="drag"><div class="click">drag and release me</div></div>

So now the 2 elements are not related directly but, if the user drags on red I don't want the inner element to get a click event. Also note in my real code there are lots of child elements that I don't want to receieve click events in the same way (parent is the drag target). Note: again, in the example above I'm just trying to stop all click events (calling preventDefault) and failing.

I can think of lots of hacky solutions, for example 2 are.

  • In the first mousemove event, search all children for click event listeners, remove all of them, on mouseup restore them (possibly after a timeout)

  • In mouseup, set a flag to ignore clicks and set a timeout to clear the flag, have all click listeners no-op if the flag is set.

Both of those require a bunch of coordination.

In the first, I'd need to write some kind of system to keep track of click handlers and the elements they are on so I can save and restore them so instead of elem.addEventListener('click', someHandler) it would have to be more like registerClickListener(elem, someHandler). Not hard but if I forget then it fails.

In the second I'd have to remember to always check some global variable in every listener implemention.

elem.addEventListener('click', () => {
  if (ignoreClicks) return;
  ...
})

Again if I forget then it fails. By forget I mean much much deeper in the DOM in unrelated code.

Both seem semi error prone so wondering if there is some other solution I'm overlooking that works like I thought preventDefault would work.

I could wrap addEventListener so for click handlers it adds a wrapper to filter out unwanted clicks. That's less error prone but it seems overkill.

Am I missing a simpler solution?


Viewing all articles
Browse latest Browse all 72358

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>