118

I have a throbber that is to appear when a user clicks a link.

The problem is is that that same link can be clicked and dragged to be rearranged. In this case, I wouldn't need the throbber to appear. It only needs to appear if its actually waiting to go somewhere.

How can I, with jQuery, create an event listener that would only allow a throbber to appear if its a click through to a link, and not a click and drag?

Trip
  • 26,756
  • 46
  • 158
  • 277
  • Perhaps OOT? I've tried several approaches for drag and drop with jQuery incluing jQuery UI, my own code with mousedown, mouseup, mousemove events and the one that has worked best is https://github.com/haberman/jdragdrop. – Rafael Vega Dec 01 '13 at 18:11
  • Here's another simple solution without any heavy framework. http://blog.blakesimpson.co.uk/read/51-swipe-js-detect-touch-direction-and-distance – Chemical Programmer Aug 12 '15 at 11:36

15 Answers15

239

On mousedown, start set the state, if the mousemove event is fired record it, finally on mouseup, check if the mouse moved. If it moved, we've been dragging. If we've not moved, it's a click.

var isDragging = false;
$("a")
.mousedown(function() {
    isDragging = false;
})
.mousemove(function() {
    isDragging = true;
 })
.mouseup(function() {
    var wasDragging = isDragging;
    isDragging = false;
    if (!wasDragging) {
        $("#throbble").toggle();
    }
});

Here's a demo: http://jsfiddle.net/W7tvD/1399/

Community
  • 1
  • 1
Simen Echholt
  • 11,243
  • 2
  • 34
  • 26
  • 1
    hey..can you help me with this http://stackoverflow.com/questions/19703617/replace-mousemove-by-mousedrag/19703798?noredirect=1#19703798 – HIRA THAKUR Oct 31 '13 at 10:44
  • 3
    You might want to add a drag threshold so your clicks aren't interpreted as drags on the "a" element. Just look for a certain amount of change in an x and y mousemove event. – Ash Blue May 21 '14 at 23:45
  • Awesome, right after the wind unbind mousemove i put my code to act as soon as the start of dragging was detected. thanks you! – Valamas Nov 26 '14 at 03:48
  • 3
    I've found that in some cases and/or browsers, `mousemove` will still be fired at least once even without any movement, so I changed it to increment the variable on movement and then check for a certain threshold on `mouseup` http://jsfiddle.net/W7tvD/1649/ Works like a charm for me now, thanks! – halfbit Feb 26 '16 at 12:30
27

For some reason, the above solutions were not working for me. I went with the following:

$('#container').on('mousedown', function(e) {
    $(this).data('p0', { x: e.pageX, y: e.pageY });
}).on('mouseup', function(e) {
    var p0 = $(this).data('p0'),
        p1 = { x: e.pageX, y: e.pageY },
        d = Math.sqrt(Math.pow(p1.x - p0.x, 2) + Math.pow(p1.y - p0.y, 2));

    if (d < 4) {
        alert('clicked');
    }
})

You can tweak the distance limit to whatever you please, or even take it all the way to zero.

anewcomer
  • 975
  • 11
  • 6
  • Best answer, helped me a lot. Just a thought: Math.sqrt isn't really necessary, better working with square distances (sqrt is slow, could affect UX). – Bruno Ferreira Jul 05 '16 at 12:51
  • 2
    `sqrt` is not slow and will not affect UX, not even if you did it hundreds of times. – NateS Mar 10 '17 at 13:05
  • Yeah, worry about unnecessary square root in the inner loop of your physics engine, not your jquery click handler... – PeterT Nov 06 '18 at 09:47
25

With jQuery UI just do this!

$( "#draggable" ).draggable({
  start: function() {

  },
  drag: function() {

  },
  stop: function() {

  }
});
Hafenkranich
  • 1,696
  • 18
  • 32
GaidenFocus
  • 353
  • 4
  • 12
5
$(".draggable")
.mousedown(function(e){
    $(this).on("mousemove",function(e){
        var p1 = { x: e.pageX, y: e.pageY };
        var p0 = $(this).data("p0") || p1;
        console.log("dragging from x:" + p0.x + " y:" + p0.y + "to x:" + p1.x + " y:" + p1.y);
        $(this).data("p0", p1);
    });
})
.mouseup(function(){
    $(this).off("mousemove");
});

This solution uses the "on" and "off" functions to bind an unbind a mousemove event (bind and unbind are deprecated). You can also detect the change in mouse x and y positions between two mousemove events.

jnaklaas
  • 51
  • 1
  • 1
  • the issue with this solution is now on every `mousedown` there will be another event handler added onto `this` causing the body of that function to execute several times increasingly. – LuKenneth Dec 25 '21 at 23:02
3

Try this: it shows when is 'dragged' state. ;) fiddle link

$(function() {
    var isDragging = false;
    $("#status").html("status:");
    $("a")
    .mousedown(function() {
        $("#status").html("status: DRAGGED");        
    })
    .mouseup(function() {
        $("#status").html("status: dropped");   
    });

    $("ul").sortable();
});
Abdul Baig
  • 3,683
  • 3
  • 21
  • 48
syC
  • 31
  • 1
2
// here is how you can detect dragging in all four directions
var isDragging = false;
$("some DOM element").mousedown(function(e) {
    var previous_x_position = e.pageX;
    var previous_y_position = e.pageY;

    $(window).mousemove(function(event) {
        isDragging = true;
        var x_position = event.pageX;
        var y_position = event.pageY;

        if (previous_x_position < x_position) {
            alert('moving right');
        } else {
            alert('moving left');
        }
        if (previous_y_position < y_position) {
            alert('moving down');
        } else {
            alert('moving up');
        }
        $(window).unbind("mousemove");
    });
}).mouseup(function() {
    var wasDragging = isDragging;
    isDragging = false;
    $(window).unbind("mousemove");
});
Joseph
  • 1,458
  • 15
  • 20
2

Make sure you set the element's draggable attribute to false so you don't have side effects when listening to mouseup events:

<div class="thing" draggable="false">text</div>

Then, you can use jQuery:

$(function() {
  var pressed, pressX, pressY,
      dragged,
      offset = 3; // helps detect when the user really meant to drag

  $(document)
  .on('mousedown', '.thing', function(e) {
    pressX = e.pageX;
    pressY = e.pageY;
    pressed = true;
  })
  .on('mousemove', '.thing', function(e) {
    if (!pressed) return;
    dragged = Math.abs(e.pageX - pressX) > offset ||
              Math.abs(e.pageY - pressY) > offset;
  })
  .on('mouseup', function() {
    dragged && console.log('Thing dragged');
    pressed = dragged = false;
  });
});
2

I branched off from the accepted answer to only run when the click is being HELD down and dragged.

My function was running when I wasn't holding the mouse down. Here's the updated code if you also want this functionality:

var isDragging = false;
var mouseDown = false;

$('.test_area')
    .mousedown(function() {
        isDragging = false;
        mouseDown = true;
    })
    .mousemove(function(e) {
        isDragging = true;

        if (isDragging === true && mouseDown === true) {
            my_special_function(e);
        }
     })
    .mouseup(function(e) {

        var wasDragging = isDragging;

        isDragging = false;
        mouseDown = false;

        if ( ! wasDragging ) {
            my_special_function(e);
        }

    }
);
Jack
  • 3,271
  • 11
  • 48
  • 57
1

For this simplest way is touch start, touch move and touch end. That is working for both PC and touch device just check it in jquery documentation and hope this is the best solution for you. good luck

Anup
  • 3,283
  • 1
  • 28
  • 37
1

jQuery plugin based on Simen Echholt's answer. I called it single click.

/**
 * jQuery plugin: Configure mouse click that is triggered only when no mouse move was detected in the action.
 * 
 * @param callback
 */
jQuery.fn.singleclick = function(callback) {
    return $(this).each(function() {
        var singleClickIsDragging = false;
        var element = $(this);

        // Configure mouse down listener.
        element.mousedown(function() {
            $(window).mousemove(function() {
                singleClickIsDragging = true;
                $(window).unbind('mousemove');
            });
        });

        // Configure mouse up listener.
        element.mouseup(function(event) {
            var wasDragging = singleClickIsDragging;
            singleClickIsDragging = false;
            $(window).unbind('mousemove');
            if(wasDragging) {
                return;
            }

            // Since no mouse move was detected then call callback function.
            callback.call(element, event);
        });
    });
};

In use:

element.singleclick(function(event) {
    alert('Single/simple click!');
});

^^

ipfaffen
  • 13
  • 6
1

Late to the party, but this code also detects touch events (mobile, tablet).

$(".draggable").on('touchstart mousedown', function(ev) {
    ev.preventDefault();
    $(this).on("touchmove mousemove",function(e){
        var x = e.pageX || e.changedTouches[0].pageX;
        var y = e.pageY || e.changedTouches[0].pageY;
        var p1 = { x: x, y: y };
        var p0 = $(this).data("p0") || p1;
        console.log("dragging from x:" + p0.x + " y:" + p0.y + "to x:" + p1.x + " y:" + p1.y);
        $(this).data("p0", p0);
    });
}).on('touchend mouseup', function(ev) {
    ev.preventDefault();
    $(this).off("touchmove mousemove");
});
Silas Palmer
  • 2,687
  • 1
  • 29
  • 30
0

You don`t have to setup variable, you can just set if it is moving in data-attribute

$youtubeSlider.find('a')
    .on('mousedown', function (e) {
        $(this).data('moving', false);
    })
    .on('mousemove', function (e) {
        $(this).data('moving', true);
    })
    .on('mouseup', function (e) {
        if (!$(this).data('moving')) {
            // Open link
        }
    });
Aiphee
  • 8,904
  • 3
  • 18
  • 16
0

You've need to set a timer. When the timer times out, start the throbber and register the click. When drag occurs, clear the timer so it never completes.

Diodeus - James MacFarlane
  • 112,730
  • 33
  • 157
  • 176
0

If you're using jQueryUI - there is an onDrag event. If you're not, then attach your listener to mouseup(), not click().

Quasipickle
  • 4,383
  • 1
  • 31
  • 53
  • 1
    How would mouseup differentiate between a drag and a click? – Trip Nov 08 '10 at 21:10
  • Hmmm - yeah I guess I didn't think it through enough. Perhaps you can have one listener attached to mousedown that records the coordinates of the mouse. Then if the coordinates of the mouse on mouseup are the same (or really close), consider it a click through. – Quasipickle Nov 09 '10 at 15:50
  • This does not work: http://stackoverflow.com/questions/12231147/jquery-mouseup-not-being-detected-correctly – Chuck Le Butt Oct 02 '14 at 14:29
  • The answer is also 4 years old - consider that. – Quasipickle Oct 02 '14 at 23:26
0

I needed a function that always keeps track of mouse position and detect left-, right-, top-, bottom- dragging. It also does not trigger on click but needs a minimum of 15px move

/**
 * Check for drag when moved minimum 15px
 * Same time keep track of mouse position while dragging
 */
// Variables to be accessed outside in other functions
var dragMouseX;
var dragMouseY;
var myDragging = false; // true or false
var dragDirectionX = false; // left or right
var dragDirectionY = false; // top or bottom

$(document).on("mousedown", function(e) {
    // Reset some variables on mousedown
    var lastDirectionCheck = e.timeStamp;
    var dragStartX = e.pageX;
    var dragStartY = e.pageY;
    dragMouseX = e.pageX;
    dragMouseY = e.pageY;
    myDragging = false;
    dragDirectionX = false;
    dragDirectionY = false;

    // On the move
    $(document).on("mousemove", function(e) {
        dragMouseX = e.pageX;
        dragMouseY = e.pageY;

        // Recalculate drag direction every 200ms in case user changes his mind
        if (e.timeStamp > (lastDirectionCheck + 200)) {
            dragStartX = dragMouseX;
            dragStartY = dragMouseY;
            lastDirectionCheck = e.timeStamp;
        }

        // Check for drag when moved minimum 15px in any direction
        if (!myDragging && Math.abs(dragStartX - dragMouseX) > 15 || Math.abs(dragStartY - dragMouseY) > 15) {
            myDragging = true;
        }
        if (myDragging) {
            // Check drag direction X
            if (dragStartX > dragMouseX) dragDirectionX = 'left';
            if (dragStartX < dragMouseX) dragDirectionX = 'right';

            // Check drag direction Y
            if (dragStartY > dragMouseY) dragDirectionY = 'top';
            if (dragStartY < dragMouseY) dragDirectionY = 'bottom';

            // console.log(dragDirectionX + ' ' + dragDirectionY);
        }
    });
});

// Reset some variables again on mouseup
$(document).on("mouseup", function() {
    $(document).off("mousemove");
    myDragging = false;
    dragDirectionX = false;
    dragDirectionY = false;
});
Julesezaar
  • 2,658
  • 1
  • 21
  • 21