最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Prevent click event if dragged - Stack Overflow

programmeradmin3浏览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>

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?

Share Improve this question asked Jan 28, 2020 at 21:42 user128511user128511
Add a ment  | 

4 Answers 4

Reset to default 3

I think how it works is that click fires after a mouseup and mousedown occur on the same element and doesn't wait to see what they do (e.g. trying to stop the click from happening).

The easiest way I've seen to stop this from happening is by disabling pointer events for that element while dragging. It changes the cursor to default while dragging which isn't optimal but that might be avoidable and this is still my fav solution. For example:

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

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

function dragStart(e) {
  dragTarget = this;
  dragTarget.classList.add("dragging");
  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});
  return 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) {
  dragTarget.classList.remove("dragging");
  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;
}
.dragging {
  pointer-events: none;
}
<div class="drag"><div class="click">drag and release me</div></div>

Some of the other options you were thinking of going down are certainly possible but this is the simplest in my opinion that should work for a lot of use cases. I got the idea for this solution from this answer here: https://stackoverflow./a/24273710/12259250 I believe some of the other answers to that question mimicked some of your alternatives so you can check those out too.

There is an optional third argument for addEventListener called capture which indicates that your function is called before the event function of any other element. By setting it to true and apply on your root element (window) you can simply shut off all click events by setting a flag.

let ignoreClicks = true; // switch me to turn on click events

document.getElementById ('d1').addEventListener ('click', function() { console.log ('d1'); });
document.getElementById ('d2').addEventListener ('click', function() { console.log ('d2'); });

window.addEventListener ('click', function (event) {
	if (ignoreClicks) event.stopPropagation();
}, true ); 
<div id="d1" style="background-color:red;width:200px;height:100px;"></div>
<div id="d2" style="background-color:green;width:200px;height:100px;"></div>

Let me provide a rather simple solution that worked for me.

  1. define a new variable.

    var isBetweenDragAndClick = false;

  2. In mousemove event for dragging, change it to true.

     document.addEventListener('mousemove', function(e) {
         // there should be some other logic for your dragging
         isBetweenDragAndClick = true;
     });
    
  3. At the beginning of click event, if this variable is true, meaning it has just been dragged, reset it to false and don't do anything.

     container.addEventListener('click', function() {
             if(isBetweenDragAndClick) {
                 isBetweenDragAndClick = false;
                 return;
             }
     // the rest of your click logic go here
     });
    

This way works with touch events as well as mouse events. It also allows clicks with a small drag, which can happen with sensitive mice.

In typescript first add a couple of properties:

private _ptDown: number[] = [0, 0];
private _ptUp: number[] = [0, 0];

Then track pointerdown and pointer up events like this:

    div.addEventListener("pointerdown", e => {
        this._ptDown = [e.offsetX, e.offsetY];
    });
    div.addEventListener("pointerup", e => {
        this._ptUp = [e.offsetX, e.offsetY];
    });

Then modify your click handler like this:

    div.addEventListener("click", e => {
        let {_ptDown, _ptUp} = this;
        let d = Math.hypot(_ptUp[0] - _ptDown[0], _ptUp[1] - _ptDown[1]);
        if (d > 4)
            return;
        // Rest of of click handling...
    });
发布评论

评论列表(0)

  1. 暂无评论