Canceling a custom drag function when mouse slides off div - javascript

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/

Related

Mousemove within mousedown stops mouse up firing

I'm capturing the mouse position within a SVG group (<g>) while the mouse is held down.
However, the mousemove continues firing after the mouse button is released and any attempt to attach a mouseup event is ignored.
var ternary = d3.select("#ternary");
var pointer;
var selector = ternary.append("g");
selector.on("mousedown", function() {
console.log('down');
selector.on("mousemove", function() {
console.log('move');
var mouse = d3.mouse(this);
console.log(mouse[0])
console.log(mouse[1]);
// continues firing after mouse button released
// how do I clear?
});
selector.on("mouseup", function() {
alert('release');
//this event doesnt fire
});
});
Two considerations:
A <g> element, as a container, will take the size of its content, which are the responsible for the mouse events;
The mouseup does fire, as you can see here (only changing the <g> for the <svg>):
var selector = d3.select("svg");
selector.on("mousedown", function() {
console.log('down');
selector.on("mousemove", function() {
console.log('move');
var mouse = d3.mouse(this);
console.log(mouse[0])
console.log(mouse[1]);
// continues firing after mouse button released
// how do I clear?
});
selector.on("mouseup", function() {
alert('release');
//this event doesnt fire
});
});
svg {
background-color: tan;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Therefore, you have just one answerable question here: "[mousemove] continues firing after mouse button released, how do I clear [it]?".
Just pass null to the event handler:
selector.on("mousemove", null);
Here is the demo:
var selector = d3.select("svg");
selector.on("mousedown", function() {
console.log('down');
selector.on("mousemove", function() {
console.log('move');
var mouse = d3.mouse(this);
console.log(mouse[0])
console.log(mouse[1]);
// continues firing after mouse button released
// how do I clear?
});
selector.on("mouseup", function() {
selector.on("mousemove", null);
alert('release');
//this event doesnt fire
});
});
svg {
background-color: tan;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

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;
}
}
});

Cancel incoming jQuery click event if mouse is moved after mousedown

I'm trying to prevent a click event from firing if the mouse is moved after the 'mousedown' event. Currently I'm doing everything manually via conditionals and booleans. I still don't have it working how I want, and I feel it's just a poor approach to accomplishing this.
var mousemove = false;
var mousedown = false;
var cancelClick = false;
$('.example').click( function() {
if (!cancelClick) {
if ( $(this).attr('id') === 'example-green') {
$(this).attr('id', 'example-blue');
} else {
$(this).attr('id', 'example-green');
}
}
cancelClick = false;
});
$('.example').mousedown( function() {
mousedown = true;
});
$('.example').mouseup( function() {
if (mousemove) {
cancelClick = true;
}
mousedown = false;
mousemove = false;
});
$('.example').mousemove( function() {
if (mousedown) {
mousemove = true;
}
});
http://jsfiddle.net/aGf6G/4/
Is there is a simpler way to achieve this? Preferably one that prevents the click events from being processed, or removes them from the pending event queue (I'm not sure if they are queued until after you release the mouse). That way the callbacks themselves aren't coupled with the implementation of this.
I would just store the x/y coordinates of the mouse on mousedown and compare it to the current coordinates in click.
$('.example')
.on('mousedown', function() {
$(this).data("initcoords", { x: event.clientX, y: event.clientY });
})
.on('click', function() {
var initCoords = $(this).data("initcoords") || { x: 0, y: 0 };
if (event.clientX === initCoords.x && event.clientY === initCoords.y) {
if ( $(this).attr('id') === 'example-green') {
$(this).attr('id', 'example-blue');
} else {
$(this).attr('id', 'example-green');
}
$(this).data('initcoords', {x:-1, y:-1});
}
});
http://jsfiddle.net/zp2y2/8/
You could also toggle the click event on and off. It is a little more concise but I wonder about the overhead of setting up event handlers compared to the method above.
$('.example')
.on('mousedown', function() { $(this).one("click", handleClick); })
.on('mousemove mouseout', function() { $(this).off('click'); });
function handleClick(){
var $el = $('.example');
if ( $el.attr('id') === 'example-green') {
$el.attr('id', 'example-blue');
} else {
$el.attr('id', 'example-green');
}
}
http://jsfiddle.net/du7ZX/
EDIT: http://api.jquery.com/event.stopimmediatepropagation/ Here is one that stops all events on one element from executing except the one you want.
If the differnt events are not all on the same element but rather spread among child/parent you could:
Event.stopPropagation() will stop all other events except the one you actually want.
I believe this here is your solution: http://api.jquery.com/event.stoppropagation/
Here is a jsfiddle to actually test with and without stopPropagation:
In this example I show how a div within a div inherits the event from his parent. Notice in the second example if you mouse over the inner div first, you will get two alerts. If you mouseover the inner div in the first example you will only get one alert.
http://jsfiddle.net/Grimbode/vsKM9/3/
/** test with stopprogation **/
$('#test').on('mouseover', function(event){
event.stopPropagation();
alert('mouseover 1');
});
$('#test2').on('mouseover', function(event){
event.stopPropagation();
alert('mouseover 2');
});
/*** test with no stoppropagation ***/
$('#test3').on('mouseover', function(event){
alert('mouseover 3');
});
$('#test4').on('mouseover', function(event){
alert('mouseover 4');
});
You could also use .off() method that removes events on a specific element.
Here's another option, I tested it and it works well:
$('.example')
.on('mousedown', function() {
$(this).data("couldBeClick", true );
})
.on('mousemove', function() {
$(this).data("couldBeClick", false );
})
.on('click', function() {
if($(this).data("couldBeClick")) {
alert('this is really a click !');
}
});

checking if touchend comes after a drag

I have some code which changes the class of a table. On a phone, sometimes the table will be too wide for the screen and the user will drag/scroll about to see the contents. However, when they touch and drag the table around, it triggers touchend on every drag.
How do I test to see whether the touchend came as a result of a touch-drag? I tried tracking dragstart and dragend but I couldn't get that to work and it seems an inelegant approach. Is there something I could add to below which would essentially determine, "Did this touchend come at the end of a drag?"
$("#resultTable").on("touchend","#resultTable td",function(){
$(this).toggleClass('stay');
});
My thanks in advance for your help.
PS - using latest jquery, and while a regular click works, it is very slow in comparison to touchend.
Use two listeners:
First set a variable to false:
var dragging = false;
Then ontouchmove set dragging to true
$("body").on("touchmove", function(){
dragging = true;
});
Then on drag complete, check to see if dragging is true, and if so count it as a dragged touch:
$("body").on("touchend", function(){
if (dragging)
return;
// wasn't a drag, just a tap
// more code here
});
The touch end will still fire, but will terminate itself before your tap script is run.
To ensure the next time you touch it isn't already set as dragged, reset it back to false on touch down.
$("body").on("touchstart", function(){
dragging = false;
});
Looks like one solution to my problem is found here:
http://alxgbsn.co.uk/2011/08/16/event-delegation-for-touch-events-in-javascript/
This bit of code detects any move after touchstart in order to abort tap behavior after tapend.
var tapArea, moved, startX, startY;
tapArea = document.querySelector('#list'); //element to delegate
moved = false; //flags if the finger has moved
startX = 0; //starting x coordinate
startY = 0; //starting y coordinate
//touchstart
tapArea.ontouchstart = function(e) {
moved = false;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
};
//touchmove
tapArea.ontouchmove = function(e) {
//if finger moves more than 10px flag to cancel
//code.google.com/mobile/articles/fast_buttons.html
if (Math.abs(e.touches[0].clientX - startX) > 10 ||
Math.abs(e.touches[0].clientY - startY) > 10) {
moved = true;
}
};
//touchend
tapArea.ontouchend = function(e) {
e.preventDefault();
//get element from touch point
var element = e.changedTouches[0].target;
//if the element is a text node, get its parent.
if (element.nodeType === 3) {
element = element.parentNode;
}
if (!moved) {
//check for the element type you want to capture
if (element.tagName.toLowerCase() === 'label') {
alert('tap');
}
}
};
//don't forget about touchcancel!
tapArea.ontouchcancel = function(e) {
//reset variables
moved = false;
startX = 0;
startY = 0;
};
More here:
https://developers.google.com/mobile/articles/fast_buttons
I would say you can't tell the difference when the user drags to see more content or drag the element arround. I think you should change the approach. You could detect if it's a mobile device and then draw a switch that will enable/disable the movement of the element.
To shorten the solution of #lededge, this might help.
$("body").on("touchmove", function(){
dragging = true;
}).on("touchend", function(){
if (dragging)
return;
}).on("touchstart", function(){
dragging = false;
});

jQuery - don't fire 'click' on 'mousemove'

I created a Fiddle to demonstrate my situation.
I want to not fire the click event when the user is panning--only if it's just a simple click. I've experimented with different placements of .off() and .on() to no avail.
Thanks in advance for your help.
http://jsfiddle.net/Waxen/syTKq/3/
Updated your fiddle to do what you want. I put the re-binding of the event in a timeout so it wouldn't trigger immediately, and adjusted the mousemove to
In on click event, you can detect whether mouse was pressed DOWN or UP. So let's analyse:
DRAG:
mouse down
mosue position changes
mouse up
CLICK:
mouse down
mouse up
You see - the difference is changed mouse position. You can record click coordinate in mouse down and then compare it when muse goes back up. If it is within some treshold, the action was a click.
The only way to tell between a "click" and a "pan" would be the time the mouse has spent held down. You could create a Date in the mousedown, then another in the mouseup, and only fire your click (zoom) event if the difference between the two dates is greater than some threshold (i would guess 1/10 of a second, but you may want to experiment)
I added a "panning" bool for a solution to your problem:
see http://jsfiddle.net/syTKq/4/
Basically, if the user has mousedown and mousemove, then panning is true. once mouseup panning is false. if just mousedown, panning is false, therefore zoom.
This solution solves your problem:
var bClicking = false,
moved = false;;
var previousX, previousY;
var $slider = $('#slider'),
$wrapper = $slider.find('li.wrapper'),
$img = $slider.find('img.foo');
$img.on('click', function()
{
if(!moved)
{
doZoom();
}
});
$wrapper.mousedown(function(e) {
e.preventDefault();
previousX = e.clientX;
previousY = e.clientY;
bClicking = true;
moved = false;
});
$(document).mouseup(function(e) {
bClicking = false;
});
$wrapper.mousemove(function(e) {
if (bClicking)
{
moved = true;
var directionX = (previousX - e.clientX) > 0 ? 1 : -1;
var directionY = (previousY - e.clientY) > 0 ? 1 : -1;
$(this).scrollLeft($(this).scrollLeft() + 10 * directionX);
$(this).scrollTop($(this).scrollTop() + 10 * directionY);
previousX = e.clientX;
previousY = e.clientY;
}
});
function doZoom() {
$img.animate({
height: '+=300',
width: '+=300'
}, 500, function() {
//animation complete
});
}​
Basically, it calls doZoom() only when the mouse has not moved between the mousedown and the mouseup events.
You can use the mousemove/mousedown events to set a flag that can be used in the click event handler to determine if the user was clicking or panning. Something like:
//set a flag for the click event to check
var isClick = false;
//bind to `mousedown` event to set the `isClick` flag to true
$(document).on('mousedown', function (event) {
isClick = true;
//bind to `mousemove` event to set the `isClick` flag to false (since it's not a drag
}).on('mousemove', function () {
isClick = false;
//bind to `click` event, check to see if the `isClick` flag is set to true, if so then this is a click, otherwise this is a drag
}).on('click', function () {
if (isClick) {
console.log('click');
} else {
console.log('drag');
}
});​​
Here is a demo: http://jsfiddle.net/SU7Ef/

Categories

Resources