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ąć.

Alexander Yanovets
źródło
Podobny / zduplikowany: stackoverflow.com/questions/3486760/…
Ryan

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
    }
});
lex82
źródło
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);
        }
...
})
Alexander Yanovets
źródło
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!

Tomek
źródło
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.

kasakka
źródło
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..

Tim Baas
źródło
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");
}
});
orolo
źródło
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);
        },
Matt Styles
źródło
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.

lukesrw
źródło
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;
    }
});
jwdreger
źródło
1

W moim przypadku działało tak:

$('#draggable').draggable({
  start: function(event, ui) {
    $(event.toElement).one('click', function(e) { e.stopPropagation(); });
  }
});
user3165434
źródło
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?

alienonland
źródło
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.

geekbrit
źródło
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);
  }

});
słońce
źródło
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.

Luchux
źródło
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ł.

LiquiNUX
źródło
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();
        }
    });
Andrey
źródło