Zapobieganie zdarzeniom kliknięć za pomocą przeciągania i upuszczania jQuery


85

Mam na stronie elementy, które można przeciągać za pomocą jQuery. Czy te elementy mają zdarzenie kliknięcia, które prowadzi do innej strony (na przykład zwykłe linki).

Jaki jest najlepszy sposób, aby zapobiec uruchomieniu kliknięcia przy upuszczeniu takiego elementu, podczas gdy kliknięcie nie jest w stanie przeciągnij i upuść?

Mam ten problem z elementami, które można sortować, ale myślę, że dobrze jest mieć rozwiązanie do ogólnego przeciągania i upuszczania.

Rozwiązałem problem samodzielnie. Potem odkryłem, że to samo rozwiązanie istnieje dla Scriptaculous , ale może ktoś ma lepszy sposób, aby to osiągnąć.


Odpowiedzi:


88

Rozwiązanie, które działało dobrze i nie wymaga przerwy: (tak, jestem trochę pedantyczny ;-)

Dodam klasę markera do elementu, gdy zaczyna się przeciąganie, np. „Noclick”. Po upuszczeniu elementu wyzwalane jest zdarzenie kliknięcia - a dokładniej, jeśli przeciąganie się kończy, w rzeczywistości nie trzeba go upuszczać na prawidłowy cel. W module obsługi kliknięcia usuwam klasę znacznika, jeśli jest obecna, w przeciwnym razie kliknięcie jest obsługiwane normalnie.

$('your selector').draggable({
    start: function(event, ui) {
        $(this).addClass('noclick');
    }
});

$('your selector').click(function(event) {
    if ($(this).hasClass('noclick')) {
        $(this).removeClass('noclick');
    }
    else {
        // actual click event code
    }
});

1
Przydatny! Nie zadziałało dla mnie za jednym zamachem, prawdopodobnie dlatego, że majstrowałem przy poprzednich próbach tego samego i zajęcia kończyły się na innym elemencie niż ten, który kliknął ... ale tak czy inaczej użyłem zmiennej globalnej (która na tym skrajnie prosta strona była w porządku), i to też działało całkiem dobrze.
MSpreij

2
To doprowadziło mnie do mojego rozwiązania, po prostu użyłem funkcji danych jQuery zamiast klasy. Dzięki.
William,

3
ale przeglądarka nie wywoła zdarzenia kliknięcia za każdym razem, gdy upuścisz element
puchu

6
Aby zapisać ustawienie klasy lub danych, w module obsługi zdarzenia kliknięcia możesz również użyć: if (! $ (This) .is ('. Ui-draggable-
dragging

1
Ups, nie chcesz stop: function(event, ui) { $(this).removeClass('noclick'); }w programie obsługi do przeciągania? W przeciwnym razie nie każde przeciągnięcie i upuszczenie w innym miejscu nie pokrzyżuje kolejnego kliknięcia „Twojego selektora”.
Bob Stein

41

Rozwiązaniem jest dodanie modułu obsługi kliknięcia, który uniemożliwi propagację kliknięcia na początku przeciągania. A następnie usuń ten program obsługi po wykonaniu upuszczenia. Ostatnia czynność powinna zostać nieco opóźniona, aby zapobieganie kliknięciom działało.

Rozwiązanie do sortowania:

...
.sortable({
...
        start: function(event, ui) {
            ui.item.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.item.unbind("click.prevent");}, 300);
        }
...
})

Rozwiązanie do przeciągania:

...
.draggable({
...
        start: function(event, ui) {
            ui.helper.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
        }
...
})

2
wydaje mi się, że to nie mogło zapobiec zdarzeniu kliknięcia dla mnie, używam jquery 1.9.1 i jquery.ui 1.10.3
aaron

1
dziwne, że odpowiedziałeś na własne pytanie, a to nawet nie działa, Sasha lol.

@aaron ta odpowiedź poniżej działa: http://stackoverflow.com/a/4572831/119765
lol

Kolejna prosta odpowiedź - po prostu umieść moduł obsługi jQuery .click () po .draggable () stackoverflow.com/questions/18032136/ ...
JxAxMxIxN

1
jeśli przekażesz pomocnikowi atrybutów z wartością „clone”, unika się wyzwalania zdarzenia do elementu posortowanego po przeciągnięciu. {helper: „clone”, start, stop ... etc},
Luchux

12

Miałem ten sam problem i próbowałem wielu podejść i żadna nie działała dla mnie.

Rozwiązanie 1

$('.item').click(function(e)
{            
    if ( $(this).is('.ui-draggable-dragging') ) return false;
});  

nic dla mnie nie robi. Element jest klikany po zakończeniu przeciągania.

Rozwiązanie 2 (autorstwa Toma de Boera)

$('.item').draggable(
{   
    stop: function(event, ui) 
    {
         $( event.originalEvent.target).one('click', function(e){ e.stopImmediatePropagation(); } );
    }
});

Działa to dobrze, ale zawodzi w jednym przypadku - kiedy przechodziłem na pełny ekran po kliknięciu:

var body = $('body')[0];     
req = body.requestFullScreen || body.webkitRequestFullScreen || body.mozRequestFullScreen;
req.call(body); 

Rozwiązanie 3 (autorstwa Sashy Yanovets)

 $('.item').draggable({
        start: function(event, ui) {
            ui.helper.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
        }
})

To nie działa dla mnie.

Rozwiązanie 4 - jedyne, które działało dobrze

$('.item').draggable(
{   
});
$('.item').click(function(e)
{  
});

Tak, to wszystko - właściwa kolejność załatwia sprawę - najpierw musisz powiązać zdarzenie draggable (), a następnie click (). Nawet gdy umieściłem kod przełączający na pełny ekran w zdarzeniu click (), nadal nie przechodził do pełnego ekranu podczas przeciągania. Idealne dla mnie!


1
Jedynym, który działał dla mnie (w IE11 / Edge), było rozwiązanie 4. Dzięki!
Simon

1
# 4 to dla mnie poprawne i najprostsze rozwiązanie, bez użycia klas i zmiennych.
elektrotyp

11

Chciałbym dodać do tego, że wydaje się, że zapobieganie zdarzeniu kliknięcia działa tylko wtedy, gdy zdarzenie kliknięcia jest zdefiniowane PO zdarzeniu, które można przeciągać lub sortować. Jeśli kliknięcie zostanie dodane jako pierwsze, zostanie aktywowane po przeciągnięciu.


9

Naprawdę nie lubię używać timerów ani zapobiegać, więc zrobiłem to:

var el, dragged

el = $( '#some_element' );

el.on( 'mousedown', onMouseDown );
el.on( 'mouseup', onMouseUp );
el.draggable( { start: onStartDrag } );

onMouseDown = function( ) {
  dragged = false;
}

onMouseUp = function( ) {
  if( !dragged ) {
    console.log('no drag, normal click')
  }
}

onStartDrag = function( ) {
  dragged = true;
}

Twardy jak skała..


To było dla mnie najlepsze rozwiązanie. Miałem problemy z uruchomieniem zdarzeń kliknięcia w interfejsie użytkownika jquery, który można sortować w przeglądarce mobilnej przy użyciu jquery.ui.touch.punch.js, ale to rozwiązało to dość elegancko.
Godsmith

3

Wersja lex82, ale dla .sortable ()

 start: function(event, ui){
 ui.item.find('.ui-widget-header').addClass('noclick');
 },

i możesz potrzebować tylko:

 start: function(event, ui){
 ui.item.addClass('noclick');
 },

a oto, czego używam do przełączania:

$("#datasign-widgets .ui-widget-header").click(function(){
if ($(this).hasClass('noclick')) {
$(this).removeClass('noclick');

}
else {
$(this).next().slideToggle();
$(this).find('.ui-icon').toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
}
});

2
Wygląda na to, że interfejs użytkownika jquery dodaje klasę „ui-sortable-helper” do sortowanego elementu. Możesz więc pominąć klasę „noclick” i po prostu zrobić, jeśli ($ (this) .hasClass ('ui-sortable-helper')). Bardziej zwięzły w ten sposób
Matt De Leon

3

Możliwa alternatywa dla odpowiedzi Sashy bez zapobiegania niewypłacalności:

var tmp_handler;
.sortable({
        start : function(event,ui){
            tmp_handler = ui.item.data("events").click[0].handler;
            ui.item.off();
        },
        stop : function(event,ui){
            setTimeout(function(){ui.item.on("click", tmp_handler)}, 300);
        },

3

W interfejsie użytkownika jQuery przeciągane elementy otrzymują klasę „ui-draggable-dragging”.
Możemy zatem użyć tej klasy do określenia, czy kliknąć, czy nie, po prostu opóźnić zdarzenie.
Nie musisz używać funkcji wywołania zwrotnego „start” lub „stop”, po prostu wykonaj:

$('#foo').on('mouseup', function () {
    if (! $(this).hasClass('ui-draggable-dragging')) {
        // your click function
    }
});

Jest to wywoływane przez „najechanie myszą”, a nie „wciśnięcie myszy” lub „kliknięcie” - więc występuje niewielkie opóźnienie, może nie być idealne - ale jest to łatwiejsze niż inne sugerowane tutaj rozwiązania.


1

Po przeczytaniu tego i kilku wątków było to rozwiązanie, które wybrałem.

var dragging = false;
$("#sortable").mouseover(function() {
    $(this).parent().sortable({
        start: function(event, ui) {
            dragging = true;
        },
        stop: function(event, ui) {
            // Update Code here
        }
    })
});
$("#sortable").click(function(mouseEvent){
    if (!dragging) {
        alert($(this).attr("id"));
    } else {
        dragging = false;
    }
});

1

W moim przypadku działało tak:

$('#draggable').draggable({
  start: function(event, ui) {
    $(event.toElement).one('click', function(e) { e.stopPropagation(); });
  }
});

0

Czy próbowałeś wyłączyć łącze za pomocą event.preventDefault (); w zdarzeniu początkowym i ponowne włączenie go w zdarzeniu zatrzymanym przez przeciąganie lub upuszczanie za pomocą polecenia unbind?


0

Tylko mała zmarszczka, aby dodać do odpowiedzi podanych powyżej. Musiałem zrobić div, który zawiera przeciągany element SalesForce, ale element SalesForce ma akcję onclick zdefiniowaną w html za pomocą jakiegoś VisualForce gobbledigook.

Oczywiście narusza to zasadę „zdefiniuj akcję kliknięcia po akcji przeciągania”, więc jako obejście ponownie zdefiniowałem akcję elementu SalesForce, która ma być wyzwalana „onDblClick”, i użyłem tego kodu dla elementu div kontenera:

$(this).draggable({
        zIndex: 999,
        revert: true,
        revertDuration: 0,
        start: function(event, ui) {
                   $(this).addClass('noclick');
                }
});

$(this).click(function(){
    if( $(this).hasClass('noclick'))
    {
        $(this).removeClass('noclick');
    }
    else
    {
        $(this).children(":first").trigger('dblclick');
    }
});

Zdarzenie kliknięcia rodzica zasadniczo ukrywa potrzebę dwukrotnego kliknięcia elementu podrzędnego, pozostawiając nienaruszone wrażenia użytkownika.


0

Próbowałem tak:

var dragging = true; 

$(this).click(function(){
  if(!dragging){
    do str...
  }
});

$(this).draggable({
  start: function(event, ui) {
      dragging = true;
  },

  stop: function(event, ui) {
      setTimeout(function(){dragging = false;}, 300);
  }

});

0

pomogło mi przekazanie helpera w obiekcie opcji jako:

.sortable({
   helper : 'clone', 
   start:function(), 
   stop:function(),
   .....
});

Wydaje się, że przeciąganie elementu dom do klonowania zapobiegło propagacji zdarzenia. Nie mogłem tego uniknąć w przypadku propagacji zdarzeń, bulgotania itp. To było dla mnie jedyne działające rozwiązanie.


0

Wydarzenia onmousedown i onmouseup działały w jednym z moich mniejszych projektów.

var mousePos = [0,0];
function startClick()
{
    mousePos = [event.clientX,event.clientY];
}
        
function endClick()
{
    if ( event.clientX != mousePos[0] && event.clientY != mousePos[1] )
    {
        alert( "DRAG CLICK" );
    }
    else
    {
        alert( "CLICK" );
    }
}
<img src=".." onmousedown="startClick();" onmouseup="endClick();" />

Tak, wiem. Nie jest to najczystszy sposób, ale masz pomysł.


0

najłatwiejsze i najmocniejsze rozwiązanie? po prostu stwórz przezroczysty element na swoim możliwym do przeciągnięcia.

.click-passthrough {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: transparent;
}

element.draggable({        
        start: function () {

        },
        drag: function(event, ui) {
            // important! if create the 'cover' in start, then you will not see click events at all
                  if (!element.find('.click-passthrough').length) {
                      element.append("<div class='click-passthrough'></div>");
                  }
        },
        stop: function() {
          // remove the cover
          element.find('.click-passthrough').remove();
        }
    });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.