Prevenção de evento de clique com arrastar e soltar do jQuery

86

Tenho elementos na página que podem ser arrastados com jQuery. Esses elementos têm evento click que navega para outra página (links comuns, por exemplo).

Qual é a melhor maneira de evitar que o clique seja disparado ao soltar esse elemento, ao mesmo tempo em que permite que o clique não seja arrastado e solte?

Tenho esse problema com elementos classificáveis, mas acho que é bom ter uma solução para arrastar e soltar em geral.

Resolvi o problema sozinho. Depois disso, descobri que a mesma solução existe para o Scriptaculous , mas talvez alguém tenha uma maneira melhor de fazer isso.

Oleksandr Yanovets
fonte
Similar / duplicado: stackoverflow.com/questions/3486760/…
Ryan

Respostas:

89

Uma solução que funcionou bem para mim e que não exige tempo limite: (sim, sou um pouco pedante ;-)

Eu adiciono uma classe de marcador ao elemento quando o arrasto começa, por exemplo, 'noclick'. Quando o elemento é solto, o evento de clique é disparado - mais precisamente se o arrastar terminar, na verdade ele não precisa ser solto em um destino válido. No manipulador de cliques, removo a classe do marcador, se houver, caso contrário, o clique é processado normalmente.

$('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
fonte
1
Útil! Não funcionou para mim de uma vez, provavelmente porque eu estava bagunçando nas tentativas anteriores da mesma coisa e as aulas acabaram em um elemento diferente daquele clicado .. mas de qualquer forma, usei uma variável global (que neste extremamente página simples estava ok), e funcionou muito bem também.
MSpreij
2
Isso me levou à minha solução, apenas usei a função de dados jQuery em vez de uma classe. Obrigado.
William
3
mas o navegador não aciona o evento de clique cada vez que você solta um elemento
puchu
6
Para salvar a configuração de uma classe ou dados, no manipulador de eventos click você também pode usar: if (! $ (This) .is ('. Ui-
draggable
1
Opa, você não quer stop: function(event, ui) { $(this).removeClass('noclick'); }no manipulador arrastável? Caso contrário, nem todo arrastar e soltar em outro lugar impedirá o próximo clique em 'seu seletor'.
Bob Stein
41

A solução é adicionar um manipulador de cliques que evitará que o clique se propague no início do arrasto. E, em seguida, remova esse manipulador após a eliminação ser realizada. A última ação deve ser atrasada um pouco para que a prevenção de cliques funcione.

Solução para classificáveis:

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

Solução para arrastar:

...
.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);
        }
...
})
Oleksandr Yanovets
fonte
2
parece que isso não pôde evitar o evento click para mim, estou usando jquery 1.9.1 e jquery.ui 1.10.3
aaron
1
estranho que você respondeu sua própria pergunta, e nem está funcionando, Sasha rs.
@aaron esta resposta abaixo funciona: http://stackoverflow.com/a/4572831/119765
lol
Outra resposta simples - basta colocar o manipulador jQuery .click () após .draggable () stackoverflow.com/questions/18032136/…
JxAxMxIxN
1
se você passar o atributo helper com o valor 'clone', evita acionar o evento para o item arrastado e ordenado .. {helper: 'clone', start, stop ... etc},
Luchux
12

Eu tive o mesmo problema e tentei várias abordagens e nenhuma funcionou para mim.

Solução 1

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

não faz nada por mim. O item está sendo clicado depois de arrastado.

Solução 2 (por Tom de Boer)

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

Isso funciona bem, mas falha em um caso - quando eu estava indo para tela inteira ao clicar:

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

Solução 3 (por Sasha 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);
        }
})

Isso não funciona para mim.

Solução 4 - a única que funcionou bem

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

Sim, é isso - a ordem correta faz o truque - primeiro você precisa vincular o evento draggable () e depois click (). Mesmo quando coloquei o código de alternância de tela inteira no evento click (), ele ainda não foi para a tela inteira ao arrastar. Perfeito para mim!

Tom
fonte
1
Apenas um que funcionou para mim (no IE11 / Edge) foi a solução 4. Obrigado!
Simon de
1
# 4 é a solução correta e mais simples para mim, sem usar classes ou variáveis.
eletrótipo
11

Gostaria de acrescentar que parece que impedir o evento de clique só funciona se o evento de clique for definido DEPOIS do evento arrastável ou classificável. Se o clique for adicionado primeiro, ele será ativado ao arrastar.

Kasakka
fonte
9

Eu realmente não gosto de usar temporizadores ou prevenção, então o que fiz foi o seguinte:

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

Rocha sólida..

Tim Baas
fonte
Essa foi a melhor solução para mim. Tive problemas para fazer os eventos de clique funcionarem em uma IU do jquery classificável em um navegador da web móvel usando jquery.ui.touch.punch.js, mas isso resolveu com bastante elegância.
Godsmith
3

versão de lex82, mas para .sortable ()

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

e você só precisa de:

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

e aqui está o que estou usando para alternar:

$("#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
fonte
2
Parece que jquery UI anexa a classe 'ui-sortable-helper' ao item que está sendo classificado. Portanto, você pode deixar de fora a classe 'noclick' e apenas fazer if ($ (this) .hasClass ('ui-sortable-helper')). Mais conciso assim
Matt De Leon
3

Uma possível alternativa para a resposta de Sasha sem prevenir o inadimplemento:

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
fonte
3

Na UI jQuery, os elementos que estão sendo arrastados recebem a classe "ui-draggable-dragging".
Podemos, portanto, usar esta classe para determinar se devemos clicar ou não, apenas atrasar o evento.
Você não precisa usar as funções de retorno de chamada "iniciar" ou "parar", basta fazer:

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

Isso é acionado a partir de "mouseup", em vez de "mousedown" ou "clique" - portanto, há um pequeno atraso, pode não ser perfeito - mas é mais fácil do que outras soluções sugeridas aqui.

lukesrw
fonte
1

Depois de ler isso e alguns tópicos, essa foi a solução que escolhi.

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
fonte
1

No meu caso funcionou assim:

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

Você já tentou desabilitar o link usando event.preventDefault (); no evento de início e reativando-o no evento de arrastar parado ou no evento de soltar usando desvincular?

alienonland
fonte
0

Apenas uma pequena ruga para adicionar às respostas dadas acima. Tive que fazer um div que contenha um elemento SalesForce arrastável, mas o elemento SalesForce tem uma ação onclick definida no html por meio de algum gobbledigook VisualForce.

Obviamente, isso viola a regra "definir ação de clique após a ação de arrastar", então, como solução alternativa, redefini a ação do elemento SalesForce para ser acionada "onDblClick" e usei este código para o div do contêiner:

$(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');
    }
});

O evento de clique do pai essencialmente oculta a necessidade de clicar duas vezes no elemento filho, deixando a experiência do usuário intacta.

geekbrit
fonte
0

Eu tentei assim:

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

});
Sol
fonte
0

para mim ajudou a passar o helper no objeto options como:

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

Parece que o elemento dom de clonagem que é arrastado impediu o borbulhamento do evento. Não consegui evitar com nenhum eventoPropagação, bubbling, etc. Essa foi a única solução que funcionou para mim.

Luchux
fonte
0

Os eventos onmousedown e onmouseup funcionaram em um de meus projetos menores.

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();" />

Sim eu conheço. Não é a maneira mais limpa, mas essa é a ideia.

LiquiNUX
fonte
0

a solução mais fácil e robusta? basta criar um elemento transparente sobre o seu arrastável.

.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
fonte