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 user128511user1285114 Answers
Reset to default 3I 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.
define a new variable.
var isBetweenDragAndClick = false;
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; });
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...
});