Prevención de eventos de clic con jQuery drag and drop


85

Tengo elementos en la página que se pueden arrastrar con jQuery. ¿Estos elementos tienen un evento de clic que navega a otra página (enlaces normales, por ejemplo)?

¿Cuál es la mejor manera de evitar que el clic se dispare al soltar dicho elemento mientras se permite que el clic no se arrastre y suelte?

Tengo este problema con los elementos ordenables, pero creo que es bueno tener una solución para arrastrar y soltar en general.

He resuelto el problema por mí mismo. Después de eso, descubrí que existe la misma solución para Scriptaculous , pero tal vez alguien tenga una mejor manera de lograrlo.


Respuestas:


88

Una solución que me funcionó bien y que no requiere un tiempo de espera: (sí, soy un poco pedante ;-)

Agrego una clase de marcador al elemento cuando se inicia el arrastre, por ejemplo, 'noclick'. Cuando se suelta el elemento, se activa el evento de clic; más precisamente, si el arrastre finaliza, en realidad no es necesario colocarlo en un objetivo válido. En el controlador de clic, elimino la clase de marcador si está presente; de ​​lo contrario, el clic se maneja 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
    }
});

1
¡Útil! No funcionó para mí de una vez, probablemente porque estaba jugando en intentos anteriores con lo mismo y las clases terminaron en un elemento diferente al que se hizo clic ... pero de todos modos, usé una variable global (que en este caso extremadamente página simple estaba bien), y eso también funcionó bastante bien.
MSpreij

2
Esto me llevó a mi solución, solo usé la función de datos jQuery en lugar de una clase. Gracias.
William

3
pero el navegador no activará el evento de clic cada vez que suelte un elemento
puchu

6
Para guardar la configuración de una clase o datos, en el controlador de eventos de clic también puede usar: if (! $ (This) .is ('. Ui-draggable-dragging')) {... click action}
ChrisV

1
Vaya, ¿no quieres stop: function(event, ui) { $(this).removeClass('noclick'); }en el controlador arrastrable? De lo contrario, no todos los procesos de arrastrar y soltar en otro lugar frustrarán el siguiente clic en "su selector".
Bob Stein

41

La solución es agregar un controlador de clic que evitará que el clic se propague al inicio del arrastre. Y luego elimine ese controlador después de que se realice la caída. La última acción debe retrasarse un poco para que funcione la prevención de clics.

Solución para ordenar:

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

Solución para arrastrar:

...
.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
Parece que esto no pudo evitar el evento de clic para mí, estoy usando jquery 1.9.1 y jquery.ui 1.10.3
Aaron

1
Es extraño que hayas respondido tu propia pregunta, y ni siquiera está funcionando, Sasha lol.

@aaron, esta respuesta a continuación funciona: http://stackoverflow.com/a/4572831/119765
lol

Otra respuesta simple: simplemente coloque el controlador jQuery .click () después de .draggable () stackoverflow.com/questions/18032136/…
JxAxMxIxN

1
si pasa el asistente de atributo con el valor 'clon', evita activar el evento en el elemento arrastrado y ordenado. {helper: 'clone', start, stop ... etc},
Luchux

12

Tuve el mismo problema e intenté varios enfoques y ninguno funcionó para mí.

Solución 1

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

no hace nada por mi Se hace clic en el elemento después de que se realiza el arrastre.

Solución 2 (por Tom de Boer)

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

Esto funciona bien, pero falla en un caso, cuando estaba en pantalla completa al hacer clic:

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

Solución 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);
        }
})

Esto no funciona para mi.

Solución 4- el único que funcionó bien

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

Sí, eso es todo, el orden correcto hace el truco, primero debe vincular el evento draggable () y luego hacer clic (). Incluso cuando puse el código de alternancia de pantalla completa en el evento click (), todavía no pasaba a pantalla completa al arrastrar. ¡Perfecto para mi!


1
Solo uno que funcionó para mí (en IE11 / Edge) fue la solución 4. ¡Gracias!
Simon

1
La # 4 es la solución correcta y más simple para mí, sin usar clases ni variables.
electrotipo

11

Me gustaría agregar a esto que parece que la prevención del evento de clic solo funciona si el evento de clic se define DESPUÉS del evento que se puede arrastrar o ordenar. Si el clic se agrega primero, se activa al arrastrar.


9

Realmente no me gusta usar temporizadores o prevenir, así que lo que hice fue esto:

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

Roca sólida..


Esta fue la mejor solución para mí. Tuve problemas para hacer que los eventos de clic funcionaran en una interfaz de usuario de jquery que se puede ordenar en un navegador web móvil usando jquery.ui.touch.punch.js, pero esto lo resolvió con bastante elegancia.
Godsmith

3

versión de lex82 pero para .sortable ()

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

y es posible que solo necesite:

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

y esto es lo que estoy 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");
}
});

2
Parece que jquery UI agrega la clase 'ui-sortable-helper' al elemento que se está ordenando. Por lo tanto, puede omitir la clase 'noclick' y simplemente hacer if ($ (this) .hasClass ('ui-sortable-helper')). Más conciso de esa manera
Matt De Leon

3

Una posible alternativa para la respuesta de Sasha sin evitar el incumplimiento:

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

En jQuery UI, los elementos que se arrastran reciben la clase "ui-draggable-dragging".
Por lo tanto, podemos usar esta clase para determinar si hacer clic o no, solo retrasar el evento.
No es necesario utilizar las funciones de devolución de llamada "iniciar" o "detener", simplemente haga lo siguiente:

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

Esto se activa desde "mouseup", en lugar de "mousedown" o "click", por lo que hay un ligero retraso, puede que no sea perfecto, pero es más fácil que otras soluciones sugeridas aquí.


1

Después de leer esto y algunos hilos, esta fue la solución con la que fui.

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

En mi caso funcionó así:

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

0

¿Ha intentado deshabilitar el enlace usando event.preventDefault (); en el evento de inicio y volver a habilitarlo en el evento de arrastre detenido o en el evento de caída usando desvincular


0

Solo una pequeña arruga para agregar a las respuestas dadas anteriormente. Tuve que hacer un div que contenga un elemento de SalesForce que se pueda arrastrar, pero el elemento de SalesForce tiene una acción onclick definida en el html a través de algún gobbledigook de VisualForce.

Obviamente, esto viola la regla "definir acción de clic después de la acción de arrastre", así que como solución, redefiní la acción del elemento SalesForce para que se active "onDblClick", y usé este código para el div contenedor:

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

El evento de clic del padre esencialmente oculta la necesidad de hacer doble clic en el elemento secundario, dejando intacta la experiencia del usuario.


0

Intenté así:

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

para mí ayudó a pasar el ayudante en el objeto de opciones como:

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

Parece que la clonación del elemento dom que se arrastra impidió el burbujeo del evento. No pude evitarlo con ningún eventPropagation, burbujeo, etc. Esta fue la única solución que me funcionó.


0

Los eventos onmousedown y onmouseup funcionaron en uno de mis proyectos más pequeños.

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

Sí, lo sé. No es la forma más limpia, pero entiendes la idea.


0

la solución más sencilla y robusta? simplemente cree un elemento transparente sobre su arrastrable.

.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();
        }
    });
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.