I'm building an app in Cordova and, as many apps have, I'm trying to implement long-press events that occur after holding down on an element.
I'm using https://github.com/john-doherty/long-press-event which fires a CustomEvent called 'long-press' after holding down on an element for 1.5 seconds.
I have an element I'd like to put both a 'Click' listener and a 'long-press' listener on, similar to many mobile apps such as photo galleries or email that react differently between a click and a long-press event.
The long-press event does fire, but the 'Click' event also fires every time and I can't find how or when I should try to stop it from firing. I've tried several placements of stopDefault() and stopPropogation() to no avail.
The HTML with a listener is
<div class="grid-col grid-col--1">
<div class="grid-item bd bdrs-4 bdw-1 bdc-grey-400">
<img class="portfolio-img lightbox-img" src="https://glamsquad.sgp1.cdn.digitaloceanspaces.com/GlamSquad/artist/1/portfolio/2019-05-16-06-07-370bc89b7bfe9769740c1f68f7e103340a94aaaeaa5d6f139f841e3c022ad309de.png">
</div>
<div class="grid-item bd bdrs-4 bdw-1 bdc-grey-400">
<img class="portfolio-img lightbox-img" src="https://glamsquad.sgp1.cdn.digitaloceanspaces.com/GlamSquad/artist/1/portfolio/2019-05-16-06-07-38d8d03cc6edef043d25e9099b883cd235c823a267ab03b9e740934f06c4f87e2f.png">
</div>
</div>
while the JS code is listening for a click on a lightbox-img, or a long-press on a portfolio image
$(document).on('long-press', '.portfolio-img', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('Portfolio long press event.');
});
$(document).on('click', '.lightbox-img', imageClick);
Is there any actual way to fire the long-press event but have it cancel or stop the click event from occurring?
One way to do this, is to disable the pointer-events from your clicked element from the moment your long-press event fired, and until the next mouseup event fires on the document.
The best would probably to make it from your library, so here is a fork of this library which does now expose a preventDefaultClick() method on the CustomEvent:
(function (window, document) {
'use strict';
var timer = null;
// check if we're using a touch screen
var isTouch = (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
// switch to touch events if using a touch screen
var mouseDown = isTouch ? 'touchstart' : 'mousedown';
var mouseOut = isTouch ? 'touchcancel' : 'mouseout';
var mouseUp = isTouch ? 'touchend' : 'mouseup';
var mouseMove = isTouch ? 'touchmove' : 'mousemove';
// wheel/scroll events
var mouseWheel = 'mousewheel';
var wheel = 'wheel';
var scrollEvent = 'scroll';
// patch CustomEvent to allow constructor creation (IE/Chrome)
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
// listen to mousedown event on any child element of the body
document.addEventListener(mouseDown, function(e) {
var el = e.target;
// get delay from html attribute if it exists, otherwise default to 1500
var longPressDelayInMs = parseInt(el.getAttribute('data-long-press-delay') || '1500', 10);
// start the timer
timer = setTimeout(fireLongPressEvent.bind(el, e), longPressDelayInMs);
});
// clear the timeout if the user releases the mouse/touch
document.addEventListener(mouseUp, function() {
clearTimeout(timer);
});
// clear the timeout if the user leaves the element
document.addEventListener(mouseOut, function() {
clearTimeout(timer);
});
// clear if the mouse moves
document.addEventListener(mouseMove, function() {
clearTimeout(timer);
});
// clear if the Wheel event is fired in the element
document.addEventListener(mouseWheel, function() {
clearTimeout(timer);
});
// clear if the Scroll event is fired in the element
document.addEventListener(wheel, function() {
clearTimeout(timer);
});
// clear if the Scroll event is fired in the element
document.addEventListener(scrollEvent, function() {
clearTimeout(timer);
});
/**
* Fires the 'long-press' event on element
* #returns {void}
*/
function fireLongPressEvent() {
var evt = new CustomEvent('long-press', { bubbles: true, cancelable: true });
// Expose a method to prevent the incoming click event
var el = this;
evt.preventDefaultClick = function() {
// disable all pointer-events
el.style["pointer-events"] = "none";
// reenable at next mouseUp
document.addEventListener(mouseUp, e => {
el.style["pointer-events"] = "all";
}, {once: true});
};
// fire the long-press event
this.dispatchEvent(evt);
clearTimeout(timer);
}
}(window, document));
btn.addEventListener('click', e => console.log('clicked'));
btn.addEventListener('long-press', e => {
console.log('long-press');
e.preventDefaultClick(); // prevents the incoming 'click' event
});
<button data-long-press-delay="500" id="btn">click me</button>
But if like me yo have a mouse that does fire a bunch of events at every swipe, then you might prefer this demo where the wheel etc timeout triggers have been disabled:
(function (window, document) {
'use strict';
var timer = null;
// check if we're using a touch screen
var isTouch = (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
// switch to touch events if using a touch screen
var mouseDown = isTouch ? 'touchstart' : 'mousedown';
var mouseOut = isTouch ? 'touchcancel' : 'mouseout';
var mouseUp = isTouch ? 'touchend' : 'mouseup';
var mouseMove = isTouch ? 'touchmove' : 'mousemove';
// wheel/scroll events
var mouseWheel = 'mousewheel';
var wheel = 'wheel';
var scrollEvent = 'scroll';
// patch CustomEvent to allow constructor creation (IE/Chrome)
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
window.CustomEvent.prototype = window.Event.prototype;
}
// listen to mousedown event on any child element of the body
document.addEventListener(mouseDown, function(e) {
var el = e.target;
// get delay from html attribute if it exists, otherwise default to 1500
var longPressDelayInMs = parseInt(el.getAttribute('data-long-press-delay') || '1500', 10);
// start the timer
timer = setTimeout(fireLongPressEvent.bind(el, e), longPressDelayInMs);
});
// clear the timeout if the user releases the mouse/touch
document.addEventListener(mouseUp, function() {
clearTimeout(timer);
});
// clear the timeout if the user leaves the element
document.addEventListener(mouseOut, function() {
clearTimeout(timer);
});
// clear if the mouse moves
document.addEventListener(mouseMove, function() {
// clearTimeout(timer);
});
// clear if the Wheel event is fired in the element
document.addEventListener(mouseWheel, function() {
// clearTimeout(timer);
});
// clear if the Scroll event is fired in the element
document.addEventListener(wheel, function() {
// clearTimeout(timer);
});
// clear if the Scroll event is fired in the element
document.addEventListener(scrollEvent, function() {
// clearTimeout(timer);
});
/**
* Fires the 'long-press' event on element
* #returns {void}
*/
function fireLongPressEvent() {
var evt = new CustomEvent('long-press', { bubbles: true, cancelable: true });
// Expose a method to prevent the incoming click event
var el = this;
evt.preventDefaultClick = function() {
// disable all pointer-events
el.style["pointer-events"] = "none";
// reenable at next mouseUp
document.addEventListener(mouseUp, e => {
el.style["pointer-events"] = "all";
}, {once: true});
};
// fire the long-press event
this.dispatchEvent(evt);
clearTimeout(timer);
}
}(window, document));
btn.addEventListener('click', e => console.log('clicked'));
btn.addEventListener('long-press', e => {
console.log('long-press');
e.preventDefaultClick(); // prevents the incoming 'click' event
});
<button data-long-press-delay="500" id="btn">click me</button>
Propagation is not the problem here - it's the fact that a long click event and a regular click event are actually the same thing, so they both fire. They're triggered because you do mouse down and then mouse up. The fact that there's a longer wait between the 2 events does not stop the regular click from being fired.
The simplest way to handle this would be to set a flag that indicates whether the long-click event has been triggered, like this...
var longClickTriggered = false;
$(document).on('long-press', '.portfolio-img', (e) => {
longClickTriggered = true;
//e.preventDefault(); // these 2 lines are probably not needed now
//e.stopPropagation();
console.log('Portfolio long press event.');
});
$(document).on('click', '.lightbox-img', (e) => {
if (!longClickTriggered) {
imageClick();
}
longClickTriggered = false;
});
I commented these lines out...
e.preventDefault();
e.stopPropagation();
because I believe they were only there in your attempt to stop the click event handler from firing.
You can use a boolean, along with mousedown and mouseup listeners
var timeoutId = 0, isHold = false;
$('#ele').on('mousedown', function() {
timeoutId = setTimeout(onEleHold, 1000);
}).on('mouseup', function() {
if (!isHold) {
onEleClick();
}
isHold = false;
clearTimeout(timeoutId);
timeoutId = 0;
});
function onEleHold() {
console.log('hold');
isHold = true;
}
function onEleClick() {
console.log('click');
}
#ele {
background: black;
width: 100px;
height: 20px;
color: white;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="ele">Click Here</div>
Related
I have an SVG with <uses> not and when you touch one of them a touchstart events fires. If they move a touchmove event fires that has a timer. On touchend the timer is cleared before the value is supposed to change. However, it changes anyway.
The results of this are that the IsDraggingUnit is set to true even after the touchend is fired. I tested with an alert and it is firing successfully when ending your touch on the element.
var IsDraggingUnit = false;
var timeOutClear;
$('#Selected_Items use').on("touchstart", function(event) {
IsDraggingUnit = false;
$('#test > p').text(IsDraggingUnit);
$(this).on("touchmove", function(event) {
timeOutClear = setTimeout(function() {
IsDraggingUnit = true;
$('#test > p').text(IsDraggingUnit);
}, 500);
});
$(this).on("touchend", function(event) {
clearTimeout(timeOutClear);
if (IsDraggingUnit == false) {
fnPlotShow($(this), bookingRental, bookingExists)
}
});
});
The issue is because you are nesting the touchmove and touchend events within the touchstart event. Therefore on subsequent touchstart occurrences each event fires multiple times and the reference to the original timeout gets lost and clearTimeout() does not clear them all.
To fix this, do not make the event handlers nested, and don't forget to set isDraggingUnit back to false on touchend
var isDraggingUnit = false, timeOutReference;
$('#Selected_Items use').on({
touchstart: function(event) {
isDraggingUnit = false;
$('#test > p').text(isDraggingUnit);
},
touchmove: function(event) {
timeOutReference = setTimeout(function() {
isDraggingUnit = true;
$('#test > p').text(isDraggingUnit);
}, 500);
},
touchend: function(event) {
clearTimeout(timeOutReference);
if (!isDraggingUnit) {
fnPlotShow($(this), bookingRental, bookingExists)
}
isDraggingUnit = false;
}
});
Also note that your question states the SVG holds <uses> nodes, yet you select use. I assume this is just a typo in the question, though, as it wouldn't work at all if there was a mismatch there.
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;
}
}
});
I need to capture an event instead of letting it bubble. This is what I want:
<body>
<div>
</div>
</body>
From this sample code I have a click event bounded on the div and the body. I want the body event to be called first. How do I go about this?
Use event capturing instead:-
$("body").get(0).addEventListener("click", function(){}, true);
Check the last argument to "addEventListener" by default it is false and is in event bubbling mode. If set to true will work as capturing event.
For cross browser implementation.
var bodyEle = $("body").get(0);
if(bodyEle.addEventListener){
bodyEle.addEventListener("click", function(){}, true);
}else if(bodyEle.attachEvent){
document.attachEvent("onclick", function(){
var event = window.event;
});
}
IE8 and prior by default use event bubbling. So I attached the event on document instead of body, so you need to use event object to get the target object. For IE you need to be very tricky.
I'd do it like this:
$("body").click(function (event) {
// Do body action
var target = $(event.target);
if (target.is($("#myDiv"))) {
// Do div action
}
});
More generally than #pvnarula's answer:
var global_handler = function(name, handler) {
var bodyEle = $("body").get(0);
if(bodyEle.addEventListener) {
bodyEle.addEventListener(name, handler, true);
} else if(bodyEle.attachEvent) {
handler = function(){
var event = window.event;
handler(event)
};
document.attachEvent("on" + name, handler)
}
return handler
}
var global_handler_off = function(name, handler) {
var bodyEle = $("body").get(0);
if(bodyEle.removeEventListener) {
bodyEle.removeEventListener(name, handler, true);
} else if(bodyEle.detachEvent) {
document.detachEvent("on" + name, handler);
}
}
Then to use:
shield_handler = global_handler("click", function(ev) {
console.log("poof")
})
// disable
global_handler_off("click", shield_handler)
shield_handler = null;
I'm using Seadragon Ajax with jQuery touch event listeners.
The container has touchstart, touchmove and touchend bound to it, heres the touch start:
.bind('touchstart MSPointerDown', function(e){
var p = coord(e.originalEvent);
p.start = true;
p.scale = 1;
if(e.originalEvent.pointerType === 4) return;
else if(e.originalEvent.pointerType !== undefined) e.originalEvent.preventMouseEvent();
$(this).data(p);
e.preventDefault();
e.stopPropagation();
})
Inside the seadragon view are some buttons generated. These buttons are not firing on a tablet because of the touchstart on its container div. It works fine with a mouse.
new Seadragon.Button("Click to go", "", "", "", "", null, moveFunction, null, null, null );
I need to check to see if the touch is on a button or not before all the stuff in the touchstart function but really am not sure how.
Resolved by adding an if statement to check the number of touches as below:
.bind('touchstart MSPointerDown', function(e){
if (event.touches.length != 1) {
e.preventDefault();
e.stopPropagation();
}
var p = coord(e.originalEvent);
p.start = true;
p.scale = 1;
if(e.originalEvent.pointerType === 4) return;
else if(e.originalEvent.pointerType !== undefined) e.originalEvent.preventMouseEvent();
$(this).data(p);
})
I'm trying to implement Google's Fast button for the mobile touch events, and I seem to be stuck. I'm trying to set it up so that I can make links into fastbuttons, but I can't seem to get my library structure right. What ends up happening is the fastbutton re-inits itself when I try to run a for loop on the links.
I'm sure it's just the way I'm setting up the library. Can someone please check it out?
http://code.google.com/mobile/articles/fast_buttons.html
;(function() {
/*Construct the FastButton with a reference to the element and click handler.*/
this.FastButton = function(element, handler) {
console.log('fastbutton init');
this.element = element;
this.handler = handler;
console.log(this);
element.addEventListener('touchstart', this, false);
element.addEventListener('click', this, false);
};
/*acts as an event dispatcher*/
this.FastButton.prototype.handleEvent = function(event) {
console.log(event);
switch (event.type) {
case 'touchstart': this.onTouchStart(event); break;
case 'touchmove': this.onTouchMove(event); break;
case 'touchend': this.onClick(event); break;
case 'click': this.onClick(event); break;
}
};
/*Save a reference to the touchstart coordinate and start listening to touchmove and
touchend events. Calling stopPropagation guarantees that other behaviors don’t get a
chance to handle the same click event. This is executed at the beginning of touch.*/
this.FastButton.prototype.onTouchStart = function(event) {
event.stopPropagation();
this.element.addEventListener('touchend', this, false);
document.body.addEventListener('touchmove', this, false);
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
};
/*When /if touchmove event is invoked, check if the user has dragged past the threshold of 10px.*/
this.FastButton.prototype.onTouchMove = function(event) {
if (Math.abs(event.touches[0].clientX - this.startX) > 10 ||
Math.abs(event.touches[0].clientY - this.startY) > 10) {
this.reset(); //if he did, then cancel the touch event
}
};
/*Invoke the actual click handler and prevent ghost clicks if this was a touchend event.*/
this.FastButton.prototype.onClick = function(event) {
event.stopPropagation();
this.reset();
this.handler(event);
if (event.type == 'touchend') {
console.log('touchend');
//clickbuster.preventGhostClick(this.startX, this.startY);
}
};
this.FastButton.prototype.reset = function() {
this.element.removeEventListener('touchend', this, false);
document.body.removeEventListener('touchmove', this, false);
};
this.clickbuster = function() {
console.log('init clickbuster');
}
/*Call preventGhostClick to bust all click events that happen within 25px of
the provided x, y coordinates in the next 2.5s.*/
this.clickbuster.preventGhostClick = function(x, y) {
clickbuster.coordinates.push(x, y);
window.setTimeout(this.clickbuster.pop, 2500);
};
this.clickbuster.pop = function() {
this.clickbuster.coordinates.splice(0, 2);
};
/*If we catch a click event inside the given radius and time threshold then we call
stopPropagation and preventDefault. Calling preventDefault will stop links
from being activated.*/
this.clickbuster.onClick = function(event) {
for (var i = 0; i < clickbuster.coordinates.length; i += 2) {
console.log(this);
var x = clickbuster.coordinates[i];
var y = clickbuster.coordinates[i + 1];
if (Math.abs(event.clientX - x) < 25 && Math.abs(event.clientY - y) < 25) {
event.stopPropagation();
event.preventDefault();
}
}
};
})(this);
document.addEventListener('click', clickbuster.onClick, true);
clickbuster.coordinates = [];
Try calling the constructor with new?
new FastButton(el, function() {});