react-grid-layout / react-draggable

React draggable component
MIT License
9.02k stars 1.03k forks source link

Distinguish between click and drag #350

Open mixtur opened 6 years ago

mixtur commented 6 years ago

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.

onokje commented 5 years ago

Yes, this please!

ibc commented 5 years ago

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

bmpinto commented 5 years ago

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;
  }
/>
mixtur commented 5 years ago

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

mixtur commented 5 years ago

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 commented 5 years ago

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

ibc commented 5 years ago

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

Izhaki commented 4 years ago

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.

ilias-t commented 4 years ago

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.

ppezaris commented 4 years ago

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?

yashvijaju commented 3 years ago

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.

sankarnarayanansr commented 3 years ago

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

yashvijaju commented 3 years ago

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.

sankarnarayanansr commented 3 years ago

@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

yashvijaju commented 3 years ago

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?

sankarnarayanansr commented 3 years ago

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

yashvijaju commented 3 years ago

@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>
sankarnarayanansr commented 3 years ago

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

yashvijaju commented 3 years ago

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>)
}
sankarnarayanansr commented 3 years ago

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

yashvijaju commented 3 years ago

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

noaLeibman commented 3 years ago

@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?

yashvijaju commented 3 years ago

@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!

noaLeibman commented 3 years ago

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