react-draggable icon indicating copy to clipboard operation
react-draggable copied to clipboard

Distinguish between click and drag

Open mixtur opened this issue 6 years ago • 24 comments

Now every click triggers both onStart and onEnd. It would be nice if I could specify maximum mouse offset and/or timeout until which drag operation will not start.

mixtur avatar Jul 18 '18 11:07 mixtur

Yes, this please!

onokje avatar Dec 13 '18 19:12 onokje

Basically I cannot place any <input> element within the Draggable because when focusing it, it automatically looses the focus and it's impossible to write into it...

ibc avatar Apr 22 '19 15:04 ibc

Since every click fires the onStart and onStop callbacks we can set a flag onDrag so we know we're dragging and check it onStop, like so:

// class property
this.isDragging = false;

<Draggable
  onDrag={() => this.isDragging = true}
  onStop={() => {
    if (!isDragging) {
      // onClick stuff here
    }

    this.isDragging = false;
  }
/>

bmpinto avatar May 16 '19 09:05 bmpinto

I think you'll never enter section inside your if then.

mixtur avatar May 16 '19 10:05 mixtur

Ow. Sorry, you are right of course. But the point is to not do it manually and hide this logic inside react-draggable. Also this is a special case of minimum mouse offset. Namely zero offset. But if element weren't draggable it would trigger click even if there were any offset. For regular onClick to occur it is enough to have 'mousedown' and 'mouseup' on thee same element. So with draggable there is ambiguity on what user means if he or she does mousedown-mouseup on the same element even if there is some mousemove in between. Users aren't perfect, they can twitch a little when clicking.

mixtur avatar May 16 '19 10:05 mixtur

@ibc you can call stopPropagation for mousedown on your input.

mixtur avatar May 16 '19 10:05 mixtur

@ibc you can call stopPropagation for mousedown on your input.

Yes, but I don't want to (or just can't) pollute 3rd party components that I want to put over the draggable container.

ibc avatar May 21 '19 16:05 ibc

Another use case (where @bmpinto solution doesn't work):

We have multiple elements within a div. Some of these elements are draggable and some are clickable. Upon onStart we check if an item is draggable and if not we return false.

Problem is that if an element is clickable, it is not draggable, which means we get neither onDrag nor onStop.

As with #419, a drag also produces a superfluous click at the end.


Edit: For my use case, turns out mouseDown is the way to go when wanting to select something.

Izhaki avatar Dec 03 '19 00:12 Izhaki

Yeah this confused me as well, basically onStart prevents any click event from continuing the event capture phase, while onDrag does not. Therefore changing to former to the latter fixed the issue for me.

ilias-t avatar Aug 25 '20 23:08 ilias-t

I have a similar use-case which I've done the workarounds for, but it still provides a suboptimal user experience.

My use-case is trying to replicate the headers in the VS Code sidebar -- you can click to expand/collapse them, but you can also drag them to re-order.

You'll notice that in vscode you need to drag about 3 pixels before the clone of the element appears at your mouse location -- this works well because clicks are treated as clicks, and drags as drags.

Even with the workaround above, I find that unless I'm super-precise when clicking, and don't move the mouse even 1 pixel, I get the drag animation when what I was really doing was a "slightly sloppy" click.

I'd like the Draggable to not start firing onStart or onDrag until the mouse has moved at least minimum pixels. Is there another workaround that I'm not considering?

ppezaris avatar Sep 23 '20 17:09 ppezaris

Hey, I'm facing a similar problem with OnClick & OnDrag.

Currently, I have to click, and then the draggable piece will follow my mouse. I think it'd be a bit more intuitive if all I had to do was hold down click and drag, then release to drop the draggable piece to the dragged location. (Demo : the road pieces in https://detour-bus-site.web.app/)

How can I fix this? Drag seems to work fine on mobile devices, but creates this issue on desktop for me.

yashvijaju avatar Apr 19 '21 09:04 yashvijaju

Make your component controlled using position then you can detect click and drag by change in coordinates.

sankarnarayanansr avatar Apr 20 '21 11:04 sankarnarayanansr

Make your component controlled using position then you can detect click and drag by change in coordinates.

I am using position to control the component. However, I am unable to click and drag. Instead, I have to click and the object then follows my mouse.

yashvijaju avatar Apr 20 '21 20:04 yashvijaju

@yjaju actually your ondrag function will get the current position on movement , you can then supply that to a component based on need . https://github.com/adityasreeram007/freeform-editor check my repo

sankarnarayanansr avatar Apr 21 '21 06:04 sankarnarayanansr

Thanks so much for sharing your repo!

I have an update regarding the problem : Draggable is working perfectly with div elements, but not img elements. Draggable works with the first code snippet, not with the second. No parameters have been changed.

<Draggable ...parameters>
    <div style={{backgroundColor: 'yellow', width: '20px', height: '20px'}}/>
  </Draggable>
<Draggable ...parameters>
    <img src={key.img}
  </Draggable>

How do I fix this for img?

yashvijaju avatar Apr 26 '21 20:04 yashvijaju

@yjaju check if both the elements are absolute positioned . Try to pass the parameters as individual props than populating entirely.

sankarnarayanansr avatar Apr 26 '21 21:04 sankarnarayanansr

@adityasreeram007 both the elements are absolute positioned (they share the same css class as well).

I used background-image in div to get it working, but just curious as to why it isn't working with image tags. Here's my entire code for both variants (first one works, second one doesn't):

<Draggable key={key.img} position={{x:key.x, y:key.y}} onDrag={(e, data)=>dragRoad(e, data, index)} onStop={(e, data)=>stopDragRoad(e, data, "single")}>
 <div className="road_img" style={{backgroundImage: `url(${key.img})`, backgroundRepeat: "no-repeat"}}/>
</Draggable>
<Draggable key={key.img} position={{x:key.x, y:key.y}} onDrag={(e, data)=>dragRoad(e, data, index)} onStop={(e, data)=>stopDragRoad(e, data, "single")}>
 <img src={key.img} className="road_img" alt="Draggable Road Piece"/>
</Draggable>

yashvijaju avatar Apr 27 '21 08:04 yashvijaju

Key of two different elements in Dom should not be same. Give different key value.

sankarnarayanansr avatar Apr 27 '21 08:04 sankarnarayanansr

The keys are different! :)

I have created an array map as follows:

{road_array.map((key, index) => 
  <Draggable key={key.img} position={{x:key.x, y:key.y}} onDrag={(e, data)=>dragRoad(e, data, index)} onStop={(e, data)=>stopDragRoad(e, data, "single")}>
    <div className="road_img" style={{backgroundImage: `url(${key.img})`, backgroundRepeat: "no-repeat"}}/>
  </Draggable>)
}

yashvijaju avatar Apr 27 '21 09:04 yashvijaju

this should be perfect. I ve used the same way to render elements it works. if this doesnt work you should shift to HOC.

sankarnarayanansr avatar Apr 27 '21 09:04 sankarnarayanansr

@adityasreeram007 thanks so much, I'll shift to HOC!

yashvijaju avatar Apr 29 '21 11:04 yashvijaju

@yjaju I have the same problem- I click and then the Draggble item follows my mouse and doesn't stop, even if I click again. This happens when I pass a function to onStop prop, if I don't specify onStop it works fine. Did you solve this?

noaLeibman avatar May 04 '21 13:05 noaLeibman

@noaLeibman i fixed it by using a div block inside Draggable (for some reason, Draggable fails with images. So I added a background-image to the div and used that instead)

Could you share your Draggable code? i can take a look and try to help!

yashvijaju avatar May 05 '21 08:05 yashvijaju

@yjaju Hey! I used onStop incorrectly and returned false in my implementation.. so that was my problem. But thank you anyway!:)

noaLeibman avatar May 05 '21 11:05 noaLeibman