在mouseup事件处理程序中取消点击事件

42

我正在编写一些拖放代码,我希望在mouseup处理程序中取消点击事件。我想防止默认行为可以解决这个问题,但是点击事件仍然被触发。

有没有办法实现这个需求?


以下方法并不能解决问题:

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
  e.preventDefault();
});
$("#test").click (function (e) {
  var a = 2;
});

点击事件不是在鼠标松开之前发生的吗? - Douglas
不是那个时候发生的,而是在鼠标松开后发生的。 - Yaron
16个回答

48

使用事件捕获阶段

在要取消点击事件的元素周围放置一个元素,并为其添加捕获事件处理程序。

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演示

发生了什么:

在触发button上的点击事件之前,周围div上的点击事件被触发,因为它将自己注册到捕获阶段而不是冒泡阶段。

captureClick处理程序然后停止其click事件的传播,并防止调用按钮上的click处理程序。这正是您想要的。然后它会删除自己以进行清理。

捕获与冒泡:

捕获阶段从DOM根部向叶子节点调用,而冒泡阶段从叶子节点向根部调用(参见:精彩的事件顺序解释)。

jQuery始终将事件添加到冒泡阶段,因此我们需要在此处使用纯JS将我们的捕获事件专门添加到捕获阶段。

请记住,IE在IE9中引入了W3C的事件捕获模型,因此这不适用于IE<9。


使用当前的事件API,您无法在已添加另一个事件处理程序之前向DOM元素添加新的事件处理程序。没有优先级参数,没有安全的跨浏览器解决方案来修改事件监听器列表


1
我认为这是最干净的解决方案,不知道这种技术是否也适用于移动端的“幽灵点击”预防。我很快就要尝试一下,并留下评论。 - Zoltán Tamási
1
这正是我一直在寻找的。对移动和Web浏览器编写了一个滑动菜单。当用户从Web浏览器拖动菜单时,它也会点击菜单链接。使用这种方法解决了我的问题。尝试了所有方法,非常感谢!这应该被接受为答案! - oOo--STAR--oOo
1
你可以使用once选项来在单击后移除事件监听器。 - Teiem

31

有一个解决方案!

这种方法对我非常有效(至少在chrome中):

mousedown时,我会将当前移动的元素添加一个类,然后在mouseup时删除该类。

所有该类所做的就是设置pointer-events:none

不知何故,这使其能够正常工作并且点击事件不会被触发。


这是真正的解决方案! - Jesus Bejarano
我喜欢这种方法。 - JCF
4
看起来不错,但在Chrome 80上对我没用;pointer-events: none也取消了mouseup... - corwin.amber

9
我情况下最好的解决方案是:
let clickTime;

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

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

这给用户150毫秒的时间来释放,如果超过150毫秒,就被认为是暂停而不是点击。


1
简单而有效! - Stuart Cusack

7

我也遇到了同样的问题,没有找到解决方法。但是我想出了一个看起来有效的恶意代码。

由于 onMouseUp 处理程序似乎不能通过 preventDefault 或 stopEvent 等方式取消链接的点击,因此我们需要让链接自己取消。当拖动开始时,可以编写一个 onclick 属性,在 a 标签中返回 false,并在拖动结束时将其删除。

由于 onDragEnd 或 onMouseUp 处理程序在浏览器解释单击之前运行,因此我们需要做一些检查,确定拖动结束和单击哪个链接等。如果它在拖曳链接之外结束(我们拖动链接,以使光标不再位于链接上),则在 onDragEnd 中删除 onclick 处理程序;但如果它在拖曳链接所在位置结束(会启动单击),则让 onclick 处理程序自己删除。够复杂了吧?

以下仅为示例代码,不完整:

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

我希望这能让你明白如何实现。正如我所说,这不是一个完整的解决方案,只是在解释概念。


这绝对是这里最聪明、最优雅的解决方案!感谢您清晰地解释了这个概念。 - Gyum Fox
我猜这是最不难看的解决方法 :). 我想在这种情况下,我只是觉得浏览器的一般行为很让人恼火。谢谢您的帮助。 - Yaron

4
由于它们是不同的事件,因此您无法从 onmouseup 取消 onclick。如果您调用 preventDefaultcancelBubble 等,则会停止进一步处理 onmouseup 事件。但是,onclick 事件仍然未触发,可以说是挂起状态。

您需要的是自己的布尔标志,例如 isDragging。当拖动开始时(例如在 onmousedown 中),可以将其设置为 true。

但是,如果您直接从 onmouseup 将其重置为 false,则在接收到 onclick 事件(isDragging == false)时将不再拖动,因为 onmouseuponclick 之前触发。

因此,您需要使用短暂的超时(例如 setTimeout(function() {isDragging = false;}, 50);),以便在触发您的 onclick 事件时,isDragging 仍将是 true,并且您的 onclick 事件处理程序可以在执行其他操作之前简单地使用 if(isDragging) return false;


1
onmouseup和onclick事件将被一起排队,因此不需要超时值,您只需要创建一个超时以允许在“isDragging”重新设置为false之前调用onclick,但超时可以为0。 - Lee Kowalkowski

4
问题在于有一个元素,需要响应点击事件。同时,它还需要被拖动。但是,当它被拖动时,需要在放下时不触发点击事件。
可能有些晚了,但也许对其他人有帮助。创建一个名为“noclick”的全局变量,并将其设置为false。拖动该项时,将noclick设置为true。在单击处理程序中,如果noclick为true,则将其设置为false,然后preventDefault,return false,stopPropagation等。但是这在Chrome上无效,因为Chrome已经具有所需的行为。只需使拖动函数仅在浏览器不是Chrome时将noclick设置为true即可。
您的单击处理程序仍将被触发,但至少它有一种方法来知道它刚从拖动返回并相应地进行操作。

何时应该取消noclick?对我来说,“click”在下拉(“mouseup”)之后触发。 - panzi
1
这对我来说似乎有效:setTimeout(function() { noClick = false; }, 0); - lazd

3

选项 #1:

所有选项中最简单的可能就是使用Event.stopImmediatePropagation()。需要注意的是,当该方法被调用时,同一元素上的所有其他点击事件监听器也将被取消,但在这种特定情况下,它可以起作用。

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
});

$("#test").click (function (e) {
  // You should add a check here to only stop
  // if the element was actually moved
  e.stopImmediatePropagation();
});

$("#test").click (function (e) {
  var a = 2;
});

作为一个例子,看看这个codepen:
https://codepen.io/guidobouman/pen/bGWeBmZ 选项#2: 如果你拥有其他的点击处理程序,另一种(更安全)的解决方案是在点击处理程序中检查事件默认操作是否被阻止。
<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
});

$("#test").click (function (e) {
  // You should add a check here to only prevent
  // if the element was actually moved
  e.preventDefault();
});

$("#test").click (function (e) {
  if(e.defaultPrevented) {
    return;
  }

  var a = 2;
});

0

我使用的解决方案如下:

var disable_click = false;

function click_function(event){
    if (!disable_click){
        // your code
    }
}

function mouse_down_function(event){
    disable_click = false; // will enable the click everytime you click
    // your code
}

function mouse_up_function(event){
    // your code
}

function mouse_drag_function(event){
    disable_click = true; // this will disable the click only when dragging after clicking
   // your code
}

将每个函数根据名称附加到相应的事件!

0

我的解决方案不需要全局变量、超时或更改HTML元素,只需要使用jQuery,它在纯JS中肯定有相应的等效物。

我声明了一个用于onClick的函数。

function onMouseClick(event){
     // do sth
}

我声明一个函数用于MouseDown(你也可以在MouseUp中执行相同的操作),以决定是否处理OnClick事件。
function onMouseDown(event){
     // some code to decide if I want a click to occur after mouseup or not
    if(myDecision){
        $('#domElement').on("click", onMouseClick);
    }
    else $('#domElement').off("click");
} 

快速提示:您应该确保
$('#domElement').on("click", onMouseClick); 不会被多次执行。如果是这种情况,那么 onMouseClick 函数也将被多次调用。

除了“myDecision”将基于全局变量(或拖放过程的某个全局状态)这一事实之外 :) - Yaron

0

这是我对于在同一元素上进行拖拽和点击的解决方案。

$('selector').on('mousedown',function(){
  setTimeout(function(ele){ele.data('drag',true)},100,$(this));
}).on('mouseup',function(){
  setTimeout(function(ele){ele.data('drag',false)},100,$(this));
}).on('click',function(){
  if($(this).data('drag')){return;}
  // put your code here
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接