Prevent click event if dragged - javascript

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?

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.com/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>

Related

how to add mouseover function when mousedown is fired,and remove mouseover function when mouseup is fired javascript

window.addEventListener('mousedown' ,md ,true);
window.addEventListener('mouseup' ,mu,true);
function md(e) {
var xcoor= e.screenX;
var ycoor= e.screenY;
console.log(xcoor+'md')
window.addEventListener('mouseover' ,mo,true);
function mo(e){
var x2coor= e.screenX;
var y2coor= e.screenY;
console.log(x2coor)
}
}
function mu() {
window.removeEventListener('mousedown',md,false)
}
It sounds like someone doesn't want a thousand mouse events firing off like a machine gun. It's neat to see the console display all of those coords so rapidly but it gets old fast. If you have the mouseup event remove the mousedown event handler, you'd have to write something overly complex to rebind it or reload the page. Kinda limiting.
The example below deals with two event handlers:
Event Handler
Event
Listener
Purpose
init(e)
mousedown+ Ctrl key
window
Bind and unbind the mousemove event
evxy(e)
mousemove
window
Log: Mouse Events and XY coordinates
Basically the user can bind and unbind the mousemove event by clicking anywhere on the page with the mouse while holding down the Ctrl key (or ⌘ for Mac).
Details are commented in example
// Bind the Window object to the mousedown event
window.addEventListener('mousedown', init);
// Define a flag
let active = false;
function init(e) {
if (e.ctrlKey) { /* If the Ctrl key was pressed when there's a mouse click...*/
evxy(e); /* ...Run EVentXY...*/
if (!active) { /*...If the flag is false...*/
window.addEventListener('mousemove', evxy); /* ...Bind the mousemove event*/
active = true; /*...and set the flag to true */
} else { /* Otherwise...*/
window.removeEventListener('mousemove', evxy); /*Unbind the mousemove event*/
active = false; /*...and set the flag to false */
}
}
}
function evxy(e) {
let ev = e.type;
let xy = [e.screenX, e.screenY];
console.log(ev + ': ' + xy);
}
.as-console-row::after { width: 0; font-size: 0; }
.as-console-row-code { width: 100%; word-break: break-word; }
.as-console-wrapper { min-height: 100% !important; max-width: 25%; margin-left: 75%; }

Get deltaY from scroll event

I am trying to set up a project for use with mobile and desktop but the wheel event doesn't work on mobile, so I need to use scroll.
I know how to get the deltaY from the Wheel event:
window.addEventListener("wheel", event => console.info(event.deltaY));
How do I get the deltaY from the Scroll event?
You should memorize the last scroll position in a variable, and when the scroll event fires, you can compute the difference.
Here is a modified example for your use case from the MDN docs on scroll event:
let lastKnownScrollPosition = 0;
let deltaY = 0;
document.addEventListener('scroll', function(e) {
let ticking = false;
if (!ticking) {
// event throtteling
window.requestAnimationFrame(function() {
deltaY = window.scrollY - lastKnownScrollPosition;
lastKnownScrollPosition = window.scrollY;
console.log('deltaY', deltaY);
ticking = false;
});
ticking = true;
}
});
body {
background: lightgrey;
height: 8000px;
}
<body>
</body>
The code above uses throtteling in order to reduce the amount of fired events. Also see: Difference Between throttling and debouncing a function

How would I prevent redirecting of a href during touchmove and mousemove?

I've got an issue while I'm trying to combine touchstart and mousedown in 1 function. I've used an a tag as the target element of the function for going to the link directly when I touched or clicked the tag.
The issue is when I touch the middle of a tag, link doesn't respond. it only works when I click the element or touch the edge of the a tag, and the output fires mousedown.
In the mobile mode, try to click the edge of a tag as much as you would possible like a grey dot in the picture above. I've created an CodePen example for looking, testing and understanding better.
How would I fix this issue?
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
e.preventDefault();
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
return false;
}
apply() {
this.$el.bind('touchstart mousedown', (e) => this.start(e));
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'), {passive: false});
setSlider.apply();
a {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
<a id="anchor" href="https://google.co.uk">Tap or Click Me</a>
<p class="textbox"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
========= Progress Update ==========
I've just added move & end function then I have to click twice for moving on to the linked website. It keeps getting worse and have no idea how to solve this issue.
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
e.preventDefault();
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
this.$el.bind('touchmove mousemove', (e) => this.move(e));
this.$el.bind('touchend mouseup', (e) => this.end(e));
return false;
}
move(e) {
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
return false;
}
end(e) {
console.log('test');
this.$el.on('click');
this.$el.off('touchstart touchend');
return false;
}
apply() {
this.$el.bind('touchstart || mousedown', (e) => this.start(e));
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'));
setSlider.apply();
======== Progress Updated After Bounty (Latest) ========
After dozens of tried, I've finally figured out and solve the previous problem but I've faced up a new issue that can't draggable and redirecting instantly.
When I use the preventDefault in the start function, all of the events work fine. The only issue of this case is dragging doesn't prevent redirecting link from the a tag. It always send me to the website no matter which ways to call the functions, clicked or dragged.
when I don't use the preventDefault, dragging doesn't work. it only works clicking the elements.
My final goal is to prevent redirecting link of the a tag from the both events, touchmove and mousemove. I've been searched about on google so many times but haven't got any of the clues.
I've written an example in Codepen and this is what I've done so far:
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
var type = e.type;
if (type === 'touchstart') {
this.paragraph.text(this.paragraph.text() + ' ' + type);
} else if (type === 'mousedown') {
this.paragraph.text(this.paragraph.text() + ' ' + type);
}
}
move(e) {
var type = e.type;
}
end(e) {
var type = e.type;
if (type === 'touchend') {
console.log('touchstart enabled');
} else if (type === 'mouseup') {
console.log('mousedown enabled');
}
}
apply() {
this.$el.bind({
touchstart: (e) => this.start(e),
touchmove: (e) => this.move(e),
touchend: (e) => this.end(e),
mousedown:(e) => this.start(e),
onmousemove: (e) => this.move(e),
mouseup: (e) => this.end(e)
});
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'));
setSlider.apply();
a {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
<a id="anchor" href="https://google.co.uk">Tap or Click Me</a>
<p class="textbox"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I think I found the solution about this. I add this code for some people in the future who are trying hard to search the same problem such as me.
function start(event, etype, condition) {
console.log(etype); // track the type of event
if (!condition) event.preventDefault(); // compare etype(eventType). Set preventDefault if condition is falsy.
items.off('click');
items.on({
['touchmove mousemove']: (event) => move(event, etype, condition),
['touchend mouseup']: end
});
}
function move(event, etype, cnd) {
if (cnd) event.preventDefault();
console.log(cnd); // track the type of event from the condition
items.on('click', function(event) {event.preventDefault();});
}
function end(event) {
items.off('touchmove mousemove touchend mouseup');
}
var items = $('.item a');
items.on('touchstart mousedown', function() {
var eventType = event.type;
var condition = (eventType === 'touchstart' || eventType === 'mousedown');
start(event, eventType, condition);
});
#anchor {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
.item {
background-color: gray;
}
.item + .item {
margin-top: 10px;
}
.item a {
cursor: pointer;
display: inline-block;
background-color: blue;
color: white;
padding: 9px;
width: 100%;
height: 100%;
}
<div id="items" class="items">
<div class="item">
<a target="_blank" href="https://google.com">Anchor</a>
</div>
<div class="item">
<a target="_blank" href="https://google.com">Anchor</a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
i think i figured out little solution for you. enable the preventDefault and afterwards enable the draggable for the a tag. Let me know if this works for you.
start(e) {
e.preventDefault();
...//rest of the code
apply() {
$el.draggable("enable");
...//rest of the code

how to implement mousemove while mouseDown pressed js

I have to implement mouse move event only when mouse down is pressed.
I need to execute "OK Moved" only when mouse down and mouse move.
I used this code
$(".floor").mousedown(function() {
$(".floor").bind('mouseover',function(){
alert("OK Moved!");
});
})
.mouseup(function() {
$(".floor").unbind('mouseover');
});
Use the mousemove event.
From mousemove and mouseover jquery docs:
The mousemove event is sent to an element when the mouse pointer moves inside the element.
The mouseover event is sent to an element when the mouse pointer enters the element.
Example: (check console output)
$(".floor").mousedown(function () {
$(this).mousemove(function () {
console.log("OK Moved!");
});
}).mouseup(function () {
$(this).unbind('mousemove');
}).mouseout(function () {
$(this).unbind('mousemove');
});
https://jsfiddle.net/n4820hsh/
In pure javascript, you can achieve this with
function mouseMoveWhilstDown(target, whileMove) {
var endMove = function () {
window.removeEventListener('mousemove', whileMove);
window.removeEventListener('mouseup', endMove);
};
target.addEventListener('mousedown', function (event) {
event.stopPropagation(); // remove if you do want it to propagate ..
window.addEventListener('mousemove', whileMove);
window.addEventListener('mouseup', endMove);
});
}
Then using the function along the lines of
mouseMoveWhilstDown(
document.getElementById('move'),
function (event) { console.log(event); }
);
(nb: in the above example, you don't need the function - you could call it as mouseMoveWhilstDown(document.getElementById('move'), console.log), but you might want to do something with it other than output it to the console!)
I know that this issue was submitted and resolved approximately seven years ago, but there is a simpler solution now:
element.addEventListener('mousemove', function(event) {
if(event.buttons == 1) {
event.preventDefault();
// Your code here!
}
});
or for touch compatible devices:
element.addEventListener('touchmove', function(event) {
if(event.touches.length == 1) {
event.preventDefault();
// Your code here!
}
}
For more information on MouseEvent.buttons, click here to visit MDN Web Docs. Touch compatible devices, however, tend to listen to TouchEvents instead of MouseEvents. TouchEvent.touches.length achieves a similar effect to MouseEvent.buttons.
To provide an example, I used the following code to move an element I created. For moving an element, I used the 'mousemove' event's MouseEvent.movementX and MouseEvent.movementY to simplify the code. The 'touchmove' event does not have these so I stored the previous touch coordinates and cleared them on 'touchstart'. You can do something similar for the 'mousemove' event if desired, as the movementX and movementY values may vary across browsers.
document.addEventListener('DOMContentLoaded', () => {
var element = document.getElementById('box');
element.style.position = 'fixed';
// MouseEvent solution.
element.addEventListener('mousemove', function(event) {
if(event.buttons == 1) {
event.preventDefault();
this.style.left = (this.offsetLeft+event.movementX)+'px';
this.style.top = (this.offsetTop+event.movementY)+'px';
}
});
// TouchEvent solution.
element.addEventListener('touchstart', function(event) {
/* Elements do not have a 'previousTouch' property. I create
this property during the touchmove event to store and
access the previous touchmove event's touch coordinates. */
delete this.previousTouch;
});
element.addEventListener('touchmove', function(event) {
if(event.touches.length == 1) {
event.preventDefault();
if(typeof this.previousTouch == 'object') {
this.style.left = (this.offsetLeft+event.touches[0].pageX-this.previousTouch.x)+'px';
this.style.top = (this.offsetTop+event.touches[0].pageY-this.previousTouch.y)+'px';
}
this.previousTouch = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
}
});
});
#box {
width: 100px;
height: 100px;
padding: 1ch;
box-sizing: border-box;
background-color: red;
border-radius: 5px;
color: white;
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="box">Drag Me!</div>
</body>
</html>
Hopefully this solution is helpful to you!
The default behaviour will stop mouseMove and mouseUp from running, you can solve this by basically adding event.preventDefault() to the mousedown function
please ensure that you use the same parameter name passed in the mousedown function to trigger the preventDefault() if not it will not work , in the example below i passed event as the parameter to the mousedown function and then triggered preventDefault() by typing event.preventDefault()
let sliderImages = Array.from(document.getElementsByClassName('slidess'));
const sliderPos = sliderImages.forEach( function (slide, index) {
let mousePosStart, isDown = false;
slide.addEventListener('mousedown', mousedown)
slide.addEventListener('mousemove', mousemove)
slide.addEventListener('mouseup', mouseup)
function mousedown(event) {
if (isDown == false) {
mousePosStart = event.pageX - this.offsetLeft;
isDown = true;
event.preventDefault();
}
}
function mousemove(event) {
if (isDown == true) {
let mousePosMove = event.pageX - this.offsetLeft;
}
}
function mouseup(event) {
if (isDown === true) {
isDown = false;
let mousePosEnd = event.pageX - this.offsetLeft;
}
}
});

Canceling a custom drag function when mouse slides off div

I have a draggable function in jquery to make it so I can drag and move elements on a div. Sometimes, when dragging the mouse comes off the div and I am not able to put back down the element.
I'm trying to add a keydown event for the escape button or something so that when pressed, the same thing happens on .on("mouseup", function(event) {
I've tried doing .on("mouseup keydown", function(event) { but it doesn't catch any keys that are being pressed.
Does anyone have any ideas on how I can cancel the drag? Either by a keydown or even on a mouseup regardless of if the mouse is on the div or not that is being dragged?
Just to be clear, the problem I am having is sometimes I will be dragging the element, I will mouseup but the mouse wasn't on the element when mouseup was called. Therefore, the element is still dragging and I no longer have my finger on the mouse and I have no way to stop the element from dragging to get it back on the document.
EDIT: Here is a jsfiddle, notice I am trying to get this to work on a scaled container. youtube video showing drag glitch
(function($) {
$.fn.drags = function(opt, callback) {
opt = $.extend({
handle: "",
cursor: "move"
}, opt);
if (opt.handle === "") {
var $el = this;
} else {
var $el = this.find(opt.handle);
}
return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
if (opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
}
var z_idx = $drag.css('z-index'),
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
$drag.css('z-index', 1000).parents().on("mousemove", function(e) {
$('.draggable').offset({
top: e.pageY + pos_y - drg_h,
left: e.pageX + pos_x - drg_w
}).on("mouseup", function() {
$(this).removeClass('draggable').css('z-index', z_idx);
});
});
e.preventDefault();
}).on("mouseup", function(event) {
if (opt.handle === "") {
$(this).removeClass('draggable');
} else {
$(this).removeClass('active-handle').parent().removeClass('draggable');
}
if (typeof callback == 'function') {
alert("this is a callback");
}
});
}
})(jQuery);
Here are a few things that might work:
Instead of listening for mouseup on the target element, listen for it on document.body. That way it will fire regardless of if the cursor is over the dragged element.
If you want to cancel the drag when the cursor wanders out of the page, add an event listener for mouseleave on document.body and use it to cancel the drag.
If you make a code-pen (or similar) test case, I will be happy to dig into the code.
Edit__
Handling mouseleave on the document prevents it from getting stuck in a draggable state. It also fixes the multiplied movement that you were seeing.
$(document.body).on('mouseleave', function(){
$el.removeClass('draggable').css('z-index', z_idx);
});
Edit2__
Previous JSFiddle was incorrect.
https://jsfiddle.net/spk4523t/6/

Categories

Resources