使用 jQuery 拖放阻止单击事件

页面上有一些元素可以用 jQuery 拖动。这些元素是否有导航到另一个页面的单击事件(例如普通链接)。

什么是最好的方法,以防止点击触发删除这样的元素,同时允许点击不拖动和删除状态?

我有这个问题与可排序的元素,但认为这是好的,有一个解决方案,一般拖放。

我自己解决了这个问题。在那之后,我发现了同样的解决方案 存在的唯一理由,但也许有人有更好的方法来实现这一点。

78741 次浏览

Solution is to add click handler that will prevent click to propagate on start of drag. And then remove that handler after drop is performed. The last action should be delayed a bit for click prevention to work.

Solution for sortable:

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

Solution for draggable:

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

Have you tried disabling the link using event.preventDefault(); in the start event and re-enabling it in the drag stopped event or drop event using unbind?

I'd like to add to this that it seems preventing the click event only works if the click event is defined AFTER the draggable or sortable event. If the click is added first, it gets activated on drag.

A solution that worked well for me and that doesn't require a timeout: (yes I'm a bit pedantic ;-)

I add a marker class to the element when dragging starts, e.g. 'noclick'. When the element is dropped, the click event is triggered -- more precisely if dragging ends, actually it doesn't have to be dropped onto a valid target. In the click handler, I remove the marker class if present, otherwise the click is handled normally.

$('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's version but for .sortable()

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

and you may only need:

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

and here's what I'm using for the toggle:

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

A possible alternative for Sasha's answer without preventing default:

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

I don't really like to use timers or preventing, so what I did is this:

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

Rocksolid..

Just a little wrinkle to add to the answers given above. I had to make a div that contains a SalesForce element draggable, but the SalesForce element has an onclick action defined in the html through some VisualForce gobbledigook.

Obviously this violates the "define click action after the drag action" rule, so as a workaround I redefined the SalesForce element's action to be triggered "onDblClick", and used this code for the container div:

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

The parent's click event essentially hides the need to double-click the child element, leaving the user experience intact.

After reading through this and a few threads this was the solution I went with.

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

I had the same problem and tried multiple approaches and none worked for me.

Solution 1

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

does nothing for me. The item is being clicked after the dragging is done.

Solution 2 (by Tom de Boer)

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

This works just fine but fails in one case- when I was going fullscreen onclick:

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

Solution 3 (by 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);
}
})

This does not work for me.

Solution 4- the only one that worked just fine

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

Yep, that's it- the correct order does the trick- first you need to bind draggable() then click() event. Even when I put fullscreen toggling code in click() event it still didn't go to fullscreen when dragging. Perfect for me!

I tried like this:

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


});

In my case it worked like this:

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

In jQuery UI, elements being dragged are given the class "ui-draggable-dragging".
We can therefore use this class to determine whether to click or not, just delay the event.
You don't need to use the "start" or "stop" callback functions, simply do:

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

This is triggered from "mouseup", rather than "mousedown" or "click" - so there's a slight delay, might not be perfect - but it's easier than other solutions suggested here.

for me helped passing the helper in options object as:

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

Seems cloning dom element that is dragged prevented the bubbling of the event. I couldn´t avoid it with any eventPropagation, bubbling, etc. This was the only working solution for me.

The onmousedown and onmouseup events worked in one of my smaller projects.

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

Yes, I know. Not the cleanest way, but you get the idea.

the most easy and robust solution? just create transparent element over your draggable.

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