Cancel click event in the mouseup event handler

48,227

Solution 1

I had the same problem and didn't found a solution either. But I came up with a hack that seems to work.

Since the onMouseUp handler doesn't seem to be able to cancel the click on a link with preventDefault or stopEvent or anything, we need to make the link cancel itself. This can be done by writing an onclick attribute which returns false to the a-tag when the drag begins, and removing it when the drag ends.

And since the onDragEnd or onMouseUp handlers are run before the click is interpreted by the browser, we need to do some checking where the drag ends and which link is clicked and so on. If it ends outside the dragged link (we dragged the link so that the cursor isn't on the link anymore), we remove the onclick handler in the onDragEnd; but if it ends where the cursor is on the dragged link (a click would be initiated), we let the onclick-handler remove itself. Complicated enough, right?

NOT COMPLETE CODE, but just to show you the idea:

// event handler that runs when the drag is initiated
function onDragStart (args) {
  // get the dragged element
  // do some checking if it's a link etc
  // store it in global var or smth

  // write the onclick handler to link element
  linkElement.writeAttribute('onclick', 'removeClickHandler(this); return false;');
}

// run from the onclick handler in the a-tag when the onMouseUp occurs on the link
function removeClickHandler (element) {
  // remove click handler from self
  element.writeAttribute('onclick', null);
}

// event handler that runs when the drag ends
function onDragEnds (args) {
  // get the target element

  // check that it's not the a-tag which we're dragging,
  // since we want the onclick handler to take care of that case
  if (targetElement !== linkElement) {
    // remove the onclick handler
    linkElement.writeAttribute('onclick', null);
  }
}

I hope this gives you an idea of how this can be accomplished. As I said, this is not a complete solution, just explaining the concept.

Solution 2

Use the event capture phase

Put an element around the element you want to cancel the click event for, and add a capture event handler to it.

var btnElm = document.querySelector('button');

btnElm.addEventListener('mouseup', function(e){
    console.log('mouseup');
    
    window.addEventListener(
        'click',
        captureClick,
        true // <-- This registeres this listener for the capture
             //     phase instead of the bubbling phase!
    ); 
});

btnElm.addEventListener('click', function(e){
    console.log('click');
});

function captureClick(e) {
    e.stopPropagation(); // Stop the click from being propagated.
    console.log('click captured');
    window.removeEventListener('click', captureClick, true); // cleanup
}
<button>Test capture click event</button>

JSFiddle Demo

What happens:

Before the click event on the button is triggered the click event on the surrounding div gets fired because it registered itself for the capture phase instead of the bubbling phase.

The captureClick handler then stops the propagation of it's click event and prevents the click handler on the button to be called. Exactly what you wanted. It then removes itself for cleanup.

Capturing vs. Bubbling:

The capture phase is called from the DOM root up to the leafs while the bubbling phase is from the leafs up the root (see: wonderful explanation of event order).

jQuery always adds events to the bubbling phase that's why we need to use pure JS here to add our capture event specifically to the capture phase.

Keep in mind, that IE introduced the W3C's event capturing model with IE9 so this won't work with IE < 9.


With the current Event API you can't add a new event handler to a DOM Element before another one that was already added. There's no priority parameter and there's no safe cross-browser solution to modify the list of event listeners.

Solution 3

There is a solution!

This approach works for me very well (at least in chrome):

on mousedown I add a class to the element that is currently being moved and on mouseup I remove the class.

All that class does is sets pointer-events:none

Somehow this makes it work and click event is not fired.

Solution 4

The best solution for my situation was:

let clickTime;

el.addEventListener('mousedown', (event) => {
  clickTime = new Date();
});

el.addEventListener('click', (event) => {
  if (new Date() - clickTime < 150) {
    // click
  } else {
    // pause
  }
});

This gives the user 150ms to release, if they take longer than 150ms it's considered a pause, rather than a click

Solution 5

The problem is there's an element. It needs to respond to clicks. It also needs to be dragged. However, when it's dragged, it needs to not trigger click when it is dropped.

A little late, but maybe it'll help someone else. Make a global variable named "noclick" or something and set it to false. When dragging the item, set noclick to true. In your click handler, if noclick is true, set it to false and then preventDefault, return false, stopPropagation, etc. This won't work on Chrome though since Chrome already has the desired behavior. Just make it so that the drag function only sets noclick to true if the browser isn't Chrome.

Your click handler will still get fired, but at least it has a way to know that it just came back from drag and behave accordingly.

Share:
48,227
Yaron
Author by

Yaron

Updated on December 04, 2021

Comments

  • Yaron
    Yaron over 2 years

    Writing some drag&drop code, I'd like to cancel the click events in my mouseup handler. I figured preventing default should do the trick, but the click event is still fired.

    Is there a way to do this?


    This doesn't work:

    <div id="test">test</div>
    <script>
    $("#test").mouseup (function (e) {
      var a = 1;
      e.preventDefault();
    });
    $("#test").click (function (e) {
      var a = 2;
    });
    
  • abuduba
    abuduba over 12 years
    You can send the code? I do not know what was going on there, therefore is difficult to predict how to fix it :)
  • Yaron
    Yaron over 12 years
    I don't want to cancel the drag, I want to cancel the click if a drag happened.
  • Yaron
    Yaron over 12 years
    doesn't really. I want the click event handler to not get called at all.
  • Lee Kowalkowski
    Lee Kowalkowski almost 12 years
    The onmouseup and onclick events will be queued together, so no timeout value will be necessary, you just need to create a timeout to allow onclick to be called before isDragging is set back to false, but the timeout can be 0.
  • 1poo
    1poo over 11 years
    "I'd like to cancel the click events in my mouseup handler" - pretty clear.
  • panzi
    panzi about 11 years
    And when should noclick be unset? click fires after the drop (mouseup) for me.
  • lazd
    lazd about 11 years
    This seems to work for me: setTimeout(function() { noClick = false; }, 0);
  • Gyum Fox
    Gyum Fox over 10 years
    This is by far the most intelligent and the most elegant solution here! Thanks for clearly explaining the concept.
  • Yaron
    Yaron over 10 years
    I guess it's the least ugly workaround :). I guess I just find the general behavior of browsers in this case annoying. Thanks for the help.
  • Zoltán Tamási
    Zoltán Tamási about 8 years
    I think this is the cleanest solution, I wonder if this technique works for mobile "ghost click" prevention too. I'm gonna try it out soon, and leave a comment.
  • oOo--STAR--oOo
    oOo--STAR--oOo almost 8 years
    This was exactly what I was looking for.. Coded a sliding menu for mobile and web browser. When user drags the menu from web browser it clicked the menu links also.. Using this method solved my problem.. Tried everything, thanks so much for this! This should be the accepted answer!
  • Jesus Bejarano
    Jesus Bejarano over 7 years
    This is the real solution!
  • Yaron
    Yaron over 5 years
    Other than the fact that "myDecision" would be based on global variables (or some global state of the drag-drop process) :)
  • JCF
    JCF almost 5 years
    I like this approach.
  • corwin.amber
    corwin.amber about 4 years
    Looks nice but didn't work for me on Chrome 80; pointer-events: none also canceled the mouseup...
  • Teiem
    Teiem almost 3 years
    you can use the once option of the eventListener to remove the eventListener after the click
  • Stuart Cusack
    Stuart Cusack over 2 years
    Simple and effective!
  • Nechoj
    Nechoj over 2 years
    Hi @Michiel, welcome to SO! Just a comment on you answer: Adding an eventhandler to all objects of a web page could significantly reduce performance. Think about webpages with hundreds or thousand elements in it ...