Отменить событие клика в обработчике события mouseup

Написав код перетаскивания, я хотел бы отменить события щелчка в моем обработчике мыши. Я решил, что предотвращение по умолчанию должно помочь, но событие щелчка по-прежнему срабатывает.

Есть ли способ сделать это?


Это не работает:

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

person Yaron    schedule 27.12.2011    source источник
comment
Разве событие щелчка не происходит до того, как мышь вверх?   -  person Douglas    schedule 27.12.2011
comment
Нет.. это происходит после наведения мыши   -  person Yaron    schedule 27.12.2011


Ответы (15)


У меня была такая же проблема, и я тоже не нашел решения. Но я придумал хак, который, кажется, работает.

Поскольку обработчик onMouseUp, по-видимому, не может отменить щелчок по ссылке с помощью preventDefault или stopEvent или чего-то еще, нам нужно сделать так, чтобы ссылка отменила себя. Это можно сделать, написав атрибут onclick, который возвращает false в a-тег, когда начинается перетаскивание, и удаляет его, когда перетаскивание заканчивается.

А поскольку обработчики onDragEnd или onMouseUp запускаются до того, как щелчок будет интерпретирован браузером, нам нужно проверить, где заканчивается перетаскивание, какая ссылка нажата и так далее. Если он заканчивается за пределами перетаскиваемой ссылки (мы перетащили ссылку так, что курсор больше не находится на ссылке), мы удаляем обработчик onclick в onDragEnd; но если он заканчивается там, где находится курсор на перетаскиваемой ссылке (будет инициирован щелчок), мы позволяем обработчику 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);
  }
}

Надеюсь, это дало вам представление о том, как этого можно добиться. Как я уже сказал, это не полное решение, а просто объяснение концепции.

person Fredrik Boström    schedule 19.01.2012
comment
Это, безусловно, самое интеллектуальное и самое элегантное решение здесь! Спасибо за четкое объяснение концепции. - person Gyum Fox; 06.11.2013
comment
Я думаю, это наименее уродливый обходной путь :). Думаю, меня просто раздражает общее поведение браузеров в этом случае. Спасибо за помощь. - person Yaron; 30.11.2013

Используйте фазу захвата событий

Поместите элемент вокруг элемента, для которого вы хотите отменить событие щелчка, и добавьте к нему обработчик события захвата.

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 представил модель захвата событий W3C с IE9, поэтому она не будет работать с IE ‹ 9.


С текущим API событий вы не можете добавить новый обработчик событий к элементу DOM перед другим, который уже был добавлен. Нет параметра приоритета и нет безопасного кросс-браузерного решения для изменения списка прослушивателей событий.

person flu    schedule 29.11.2013
comment
Я думаю, что это самое чистое решение, интересно, работает ли этот метод и для предотвращения призрачных кликов на мобильных устройствах. Я собираюсь попробовать это в ближайшее время, и оставить комментарий. - person Zoltán Tamási; 23.05.2016
comment
Это было именно то, что я искал.. Закодировал скользящее меню для мобильных устройств и веб-браузера. Когда пользователь перетаскивает меню из веб-браузера, он также щелкает ссылки меню. Использование этого метода решило мою проблему.. Перепробовал все, большое спасибо за это! Это должен быть принятый ответ! - person oOo--STAR--oOo; 07.06.2016
comment
круто, спасибо большое - person Ivan Sanz-Carasa; 18.01.2021
comment
вы можете использовать опцию once для eventListener, чтобы удалить eventListener после щелчка - person Teiem; 05.06.2021

Есть решение!

Этот подход работает для меня очень хорошо (по крайней мере, в хроме):

mousedown я добавляю класс к перемещаемому элементу, а mouseup удаляю класс.

Все, что делает этот класс, это устанавливает pointer-events:none

Каким-то образом это заставляет его работать, и событие щелчка не запускается.

person FDIM    schedule 17.06.2014
comment
Это настоящее решение! - person Jesus Bejarano; 08.12.2016
comment
Мне нравится этот подход. - person JCF; 23.07.2019
comment
Выглядит красиво, но у меня не работает в Chrome 80; pointer-events: none также отменил mouseup... - person corwin.amber; 11.04.2020

Лучшим решением для моей ситуации было:

let clickTime;

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

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

Это дает пользователю 150 мс для освобождения, если это занимает больше 150 мс, это считается паузой, а не щелчком.

person Kim T    schedule 23.06.2018

Проблема в том, что есть элемент. Он должен реагировать на клики. Его тоже нужно перетащить. Однако, когда он перетаскивается, он не должен вызывать щелчок при опускании.

Немного поздно, но, возможно, это поможет кому-то еще. Создайте глобальную переменную с именем «noclick» или что-то в этом роде и установите для нее значение false. При перетаскивании элемента установите для noclick значение true. В вашем обработчике кликов, если noclick имеет значение true, установите для него значение false, а затем preventDefault, return false, stopPropagation и т. д. Однако это не будет работать в Chrome, поскольку Chrome уже имеет желаемое поведение. Просто сделайте так, чтобы функция перетаскивания устанавливала для noclick значение true только в том случае, если браузер не Chrome.

Ваш обработчик кликов по-прежнему будет срабатывать, но, по крайней мере, у него есть способ узнать, что он только что вернулся после перетаскивания, и вести себя соответственно.

person Kaylakaze    schedule 20.05.2012
comment
И когда noclick должен быть отключен? click срабатывает после падения (mouseup) для меня. - person panzi; 03.04.2013
comment
Кажется, это работает для меня: setTimeout(function() { noClick = false; }, 0); - person lazd; 06.05.2013

Поскольку это разные события, вы не можете отменить onclick из onmouseup, если вы вызовете preventDefault или cancelBubble или что-то еще, вы остановите дальнейшую обработку события onmouseup. Событие onclick все еще ожидается, так сказать, еще не запущено.

Вам нужен собственный логический флаг, например. isDragging. Вы можете установить для этого параметра значение true, когда начинается перетаскивание (например, в пределах onmousedown или где-то еще).

Но если вы сбросите это значение на false непосредственно из onmouseup, вы больше не будете перетаскивать, когда получите событие onclick (isDragging == false), потому что onmouseup сработает раньше, чем onclick.

Итак, что вам нужно сделать, это использовать короткий тайм-аут (например, setTimeout(function() {isDragging = false;}, 50);), поэтому, когда ваше событие onclick запускается, isDragging все еще будет true, а ваш обработчик события onclick может просто иметь if(isDragging) return false;, прежде чем он сделает что-либо еще.

person Lee Kowalkowski    schedule 17.06.2012
comment
События onmouseup и onclick будут поставлены в очередь вместе, поэтому значение тайм-аута не потребуется, вам просто нужно создать тайм-аут, чтобы разрешить вызов onclick до того, как isDragging вернется в значение false, но тайм-аут может быть равен 0. - person Lee Kowalkowski; 18.06.2012

Это возможно, но я не уверен, сможете ли вы справиться с таким управлением evt с помощью jQuery. Этот пример кода не должен сильно отличаться от ваших ожиданий или, по крайней мере, указывать направление.

function AddEvent(id, evt_type, ma_fonction, phase) {
    var oElt = document.getElementById(id);
    // modèle W3C mode bubbling
    if( oElt.addEventListener ) {
        oElt.addEventListener(evt_type, ma_fonction, phase);
    // modèle MSIE
    } else if( oElt.attachEvent ) {
        oElt.attachEvent('on'+evt_type, ma_fonction);
    }
    return false;
}

function DelEvent(id, evt_type, ma_fonction, phase) {
    var oElt = document.getElementById(id);
    // modèle W3C mode bubbling
    if( oElt.removeEventListener ) {
        oElt.removeEventListener(evt_type, ma_fonction, phase);
    // modèle MSIE
    } else if( oElt.detachEvent ) {
        oElt.detachEvent('on'+evt_type, ma_fonction);
    }
    return false;
}

    var mon_id = 'test';
    var le_type_evt = "mouseup";
    var flux_evt = false; // prevent bubbling
    var action_du_gestionnaire = function(e) {
        alert('evt mouseup on tag <div>');

        // 1ère méthode : DOM Lev 2
        // W3C
            if ( e.target )
                e.target.removeEventListener(le_type_evt, arguments.callee, flux_evt);
        // MSIE
            else if ( e.srcElement )
                e.srcElement.detachEvent('on'+le_type_evt, arguments.callee);

        // 2ème méthode DOM Lev2
        // DelEvent(mon_id, le_type_evt, action_du_gestionnaire, flux_evt);
    };


    AddEvent(mon_id, le_type_evt, action_du_gestionnaire, flux_evt);
person hornetbzz    schedule 30.12.2011

я недавно столкнулся с той же проблемой. Вот мое решение :)

    initDrag: function (element) {
        var moved = false,
            target = null;

        function move(e) {
            // your move code
            moved = true;
        };

        function end(e) {
            var e = e || window.event,
                current_target = e.target || e.srcElement;

            document.onmousemove = null;
            document.onmouseup = null;

            // click happens only if mousedown and mouseup has same target
            if (moved && target === current_target) 
                element.onclick = click;

            moved = false;
        };

        function click(e) {
            var e = e || window.event;

            e.preventDefault();
            e.stopPropagation();

            // this event should work only once
            element.onclick = null;
        };

        function init(e) {
            var e = e || window.event;
            target = e.target || e.srcElement;

            e.preventDefault();

            document.onmousemove = move;
            document.onmouseup = end;
        };

        element.onmousedown = init;
    };
person starl1ng    schedule 29.07.2012

Это мое решение для перетаскивания одного и того же элемента.

$('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
});
person Quyền Lê    schedule 27.06.2017

решение, которое я использую, следующее:

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
}

прикрепите каждую функцию к соответствующему событию в соответствии с названием!

person Mohamed Benkedadra    schedule 25.04.2018

Мое решение не требует глобальных переменных, тайм-аутов или изменения элементов html, просто jquery, который, безусловно, имеет некоторый эквивалент в простом js.

Я объявляю функцию для onClick

function onMouseClick(event){
     // do sth
}

Я объявляю функцию для MouseDown (вы также можете сделать то же самое с помощью мыши), чтобы решить, обрабатывать ли событие 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, он тоже будет вызываться несколько раз.

person thogu    schedule 09.09.2018
comment
Кроме того факта, что myDecision будет основан на глобальных переменных (или некотором глобальном состоянии процесса перетаскивания) :) - person Yaron; 11.09.2018

Опция 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;
});
person Guido Bouman    schedule 08.07.2021

$(document).mouseup(function(event){ // make sure to set the event parameter
    event.preventDefault(); // prevent default, like you said
});

Важно отметить параметр event.

EDIT: Вы хотите отменить перетаскивание?

Я знаю, как это сделать, либо использовать bind() (для более старых версий jQuery), либо on() (для jQuery 1.7+), чтобы прикрепить событие mousedown, а затем использовать unbind() или off() соответственно, чтобы отсоединить его.

$(document)
    .on("mousedown", function(){...})
    .on("mouseup", function(){
        $(document).off("mousedown");
    });
person Purag    schedule 27.12.2011
comment
Я не хочу отменять перетаскивание, я хочу отменить щелчок, если произошло перетаскивание. - person Yaron; 27.12.2011

Если вы хотите подавить событие щелчка, добавьте оператор event.preventDefault() только в обработчик события щелчка, а не в обработчик события mouseup. Используйте приведенный ниже код, и он заработает.

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

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

Надеюсь, это решит вашу проблему.

person AmGates    schedule 28.12.2011
comment
на самом деле нет. Я хочу, чтобы обработчик события click вообще не вызывался. - person Yaron; 28.12.2011
comment
Я хотел бы отменить события щелчка в моем обработчике мыши - довольно ясно. - person Anders Fjeldstad; 27.09.2012

person    schedule
comment
Вы можете отправить код? Я не знаю, что там происходило, поэтому сложно предсказать, как это исправить :) - person abuduba; 27.12.2011