Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mobile / touch support #11

Closed
alexreardon opened this issue Aug 10, 2017 · 41 comments
Closed

Add mobile / touch support #11

alexreardon opened this issue Aug 10, 2017 · 41 comments
Assignees

Comments

@alexreardon
Copy link
Collaborator

Currently the library only supports keyboard and mouse dragging. It would useful to add touch support so that it can be used on touch devices (eg phones and tablets)

@alexreardon
Copy link
Collaborator Author

This feature would require some additional touch event handlers and management in drag-handle.jsx.

The challenges will be:

  • choosing a touch interaction that feels natural by default
  • potentially offering some level of customisation if required (although hopefully not)
  • correctly allowing a Draggable anchor (or some other interactive element) to operate as an anchor normally while also allowing users to drag it without the interactions overlapping unnaturally (similar to the sloppy click threshold)
  • ensuring that the interaction works across the supported browser matrix. Although I am not too worried about this as the mobile browser matrix is fairly generous for mobile

I did a little spike and I am not worried about performance at this stage. The current implementation is fairly light on the CPU while dragging

@alexreardon
Copy link
Collaborator Author

alexreardon commented Aug 16, 2017

At this stage internally we are targeting a few features for our desktop applications:

  • horizontal dragging
  • transitioning between lists

So those will be the focus of our internal efforts for now as they are out immediate use cases. If somebody else wants to give it a crack let me know. I did a spike and it is fairly easy to add some touch listeners and get it all going. The challenge is making it feel great for users every time

@trevordmiller
Copy link

Thanks for your work @alexreardon! Looking forward to this.

@alexreardon alexreardon modified the milestone: Candy Castle Aug 26, 2017
@GrosSacASac
Copy link

Hi thanks for the react-beautiful-dnd plugin, good job so far.

I wonder what way you are going to use for mobile dnd. On mobile the touch and move interaction is already used for scrolling. I see 3 ways:

  • Make the dnd list horizontal only and the scrolling vertical only, or vice versa.
  • Add a little square at the end of each list item, and only touching that will start a dnd interaction
  • Use a switch mechanism normal/dragging, after a certain interaction e.g. long touch

@alexreardon
Copy link
Collaborator Author

We are looking at adopting a press and hold (long touch) mechanism to initiate touch dragging to avoid conflicts with normal scrolling. It is a similar pattern that is used in native devices. We are aiming to avoid providing any options for custom drag starting - similar to what we already do with mouse. We will investigate and decide on a approach that works well with the rest of the feature set and fits well in the physical metaphor we are going for. The long press seems nice because it is sort of like taking the time to pick something up.

/cc @jaredcrowe

@alexreardon
Copy link
Collaborator Author

We are hoping to look at this in the next few weeks! https://github.com/atlassian/react-beautiful-dnd/milestone/2

@trevordmiller
Copy link

Yay! 🎉🎉🎉

@alexreardon alexreardon self-assigned this Oct 31, 2017
@alexreardon
Copy link
Collaborator Author

🤞 I hope to start work on this when I return

@alexreardon alexreardon changed the title Add touch support Add mobile / touch support Nov 2, 2017
@alexreardon
Copy link
Collaborator Author

Making progress 🎉

screen shot 2017-11-02 at 3 42 38 pm

@alexreardon
Copy link
Collaborator Author

alexreardon commented Nov 3, 2017

The event binding code drag-handle.js was getting super hard to reason about. I have decided to split the event handling into three seperate sensors: mouse, keyboard and touch. This makes things a lot easier to reason about and avoids a lot of conditionals. I already have the mouse and keyboard back to parity with the original code. This change will significantly reduce the mental friction and maintenance burden.

Next week I will implement the touch sensor as well as ensuring all the old tests pass and that new tests are written for the new behaviour and touch sensor.

Note: this is an internal change and the api will remain the same

@mostafah
Copy link

mostafah commented Nov 3, 2017

On top of the amazing work, thank you for these status updates. I, as someone interested in this feature, appreciate being in the loop about this progress.

@trevordmiller
Copy link

Yes thank you @alexreardon and for the amazing library, your hard work, and updates! This will be very helpful :)

@alexreardon
Copy link
Collaborator Author

Glad you find them useful @mostafah @trevordmiller - I was not sure if people were interested

@phoenixbox
Copy link

@alexreardon these updates are super helpful, cant wait to try out the mobile version. This react component is one of the best Ive seen, awesome job ✨ 🙌

@alexreardon
Copy link
Collaborator Author

I am getting closer. I now have it working on latest mobile chrome, safari and firefox on ios11 🤘.

In order to manage expectations I want to call out that this initial release will not support scrolling / auto scrolling. This means that when a Draggable moves to the edge of a scroll container or the viewport a scroll will not occur. This is obviously not ideal and will be addressed when we build #27 which should hopefully be quite soon. This is a similar restriction to what we already have in place for keyboard dragging. Auto scrolling is currently scheduled as the next feature to be built.

@alexreardon
Copy link
Collaborator Author

Chrome, Safari and Firefox

mobile-example

yay

@alexreardon alexreardon mentioned this issue Nov 6, 2017
19 tasks
@alexreardon
Copy link
Collaborator Author

Things move forward and then backwards. The goal is to create an experience that feels great while also respecting the standard touch interactions as much as possible. Here is some in progress documentation:

Understanding intention: tap, force press, scroll and drag

When a user presses their finger (or other input) on a Draggable we are not sure if they where intending to tap, force press, scroll the container or drag. As much as possible react-beautiful-dnd aims to ensure that a users default interaction experience remains uneffected.

  • A user starts a drag by holding their finger 👇 on an element for a small period of time 🕑 (long press)
  • If the user lifts their finger before the timer is finished then we release the event to the browser for it to determine whether to perform the standard tap / click action. This allows you to have a Draggable that is both clickable such as a anchor as well as draggable.
  • If the user moves beyond a small threshold before the drag start timer finishes then it is determined that the user was intending to scroll rather than drag and a drag will not start. Unfortunately by the time we know this we need to opt out of native scrolling. This means that a user will not be able to scroll a container / the view port if they start the drag on a Draggable. This is unfortunate but opting out of native scrolling can only be done before we know if it was a scroll or not. This is standard in drag and drop libraries but we wanted to call it out
  • Safari only: if the user force presses on the element before they have moved the element (even if a drag has already started) then the drag is cancelled and the standard force press action occurs. For an anchor this is a website preview.

Draggable Anchors

If the element you are dragging is an anchor <a> we recommend that you add the following styles:

  • Disable tap highlight colors. By default webkit based browsers will add a grey overlay to anchors when they are active more information.
-webkit-tap-highlight-color: rgba(0,0,0,0);
  • Disable the 'open in new tab' dialog. This prevents a dialog popping when a user long presses an anchor
-webkit-touch-callout: none;

Vibration

If you like you could also trigger a vibration event when the user picks up a Draggable. This can provide tactile feedback that the user is doing something. It currently is only supported in Chrome on Android.

class App extends React.Component {
  onDragStart = () => {
    // good times
    if (window.navigator.vibrate) {
      window.navigator.vibrate(200);
    }
  };
  /*...*/
}

@alexreardon
Copy link
Collaborator Author

You can have a play with the new touch support here. Be sure to look on your touch device

@lukebatchelor
Copy link
Member

Hey Alex, tried on a Pixel on Android 8.0.0.

When unscrolled I have to hold for a while before moving or it causes a refresh.

When scrolled down I cant seem to move at all. I'll show you tomorrow if you like. :$

@mostafah
Copy link

mostafah commented Nov 9, 2017

Great. It works very good on iPhone (Safari on iOS 11.1, iPhone 7). The only problem is that I can only scroll with the blue area. Swiping over the cards does not do anything. I’m not sure if that is expected for the first version or not.

@hakunin
Copy link

hakunin commented Nov 9, 2017

I was able to long press, then start moving, but instead it scrolled the list below the card.
When I am towards the top of the page, this can cause the refresh trigger.
It seems to be based on timing because the card goes green and I think I can drag but maybe I am too fast?

A lot of times the long press doesn't grab the task, just selects it.

Android, Google Chrome.

@alexreardon
Copy link
Collaborator Author

alexreardon commented Nov 9, 2017

Drag start and scroll: we need your help!

We are trying to make a decision about how to start a touch drag. There are two primary options which would be great to get your vote on. Each option will get its own comment below. To add a vote add a 👍 reaction to the comment. The votes won't be strictly binding - but it will be a great opportunity to see what other people think. Also, if you have other suggestions or would like to comment feel free to do so.

Problem

When a user puts their finger down (or some other touch input) we need to opt out of native scrolling. Unfortunately this decision needs to be made up front and we cannot opt back into native scrolling after this initial press.

Option 1: don't start drag if user is trying to scroll

If the user moves their finger beyond a threshold within a small period of time we can safely assume they where trying to scroll rather than drag. So we do not start a drag. However, this can confuse the user as the interface can feel unresponsive as they are trying to native scroll but we have already opted out of native scrolling.

Option 2: start a drag on scroll attempt

This one is my current preference

If the user moves beyond a certain threshold (regardless of time): start a drag. This means that even if a user was trying to scroll: a drag will start. This might be annoying as the user may have been intending to scroll - but at least something will happen (a drag) rather than nothing in option 1. Given that we need to opt out of native scrolling I think this is the best option.

Also, keep in mind that once auto scrolling lands this experience will not be as bad.

Cheers

@alexreardon
Copy link
Collaborator Author

Vote: option 1 don't start a drag is a user is trying to scroll
(Even though a native scroll will not happen)

@alexreardon
Copy link
Collaborator Author

Vote: option 2 start a drag on scroll attempt
(My preference)

@hakunin
Copy link

hakunin commented Nov 9, 2017

I am a little confused, would both options allow us to implement something like trello has in their mobile experience? (They've got both scrolling the lists and dragging the tasks)

@alexreardon
Copy link
Collaborator Author

@hakunin this has to do with drag initiation. Once auto scrolling is supported then you will be able to scroll no worries

@alexreardon
Copy link
Collaborator Author

Also, Trello's mobile web experience does not have card drag and drop

@hakunin
Copy link

hakunin commented Nov 9, 2017

Ah, I was looking at their app, hoping its the same.

@alexreardon
Copy link
Collaborator Author

Yeah native is a little different. We can only achieve what the browsers / web standards let us 👍

@alexreardon
Copy link
Collaborator Author

@lukebatchelor @hakunin the experience on andriod is not great atm. I will be looking into it when I get more time

@alexreardon
Copy link
Collaborator Author

I have now swapped over to option 2 and it feels much better.

@hakunin @lukebatchelor I have added some improvements for android - please give it another go!

@lukebatchelor
Copy link
Member

It's perfect Alex! The vibration is an awesome touch! (No pun intended??)

@bradleyayers
Copy link
Contributor

Great work on this! I’m trying to follow along but I’m missing a proper understanding of the interaction with native scrolling.

I think the reason for opting out is that we want the page to stay fixed (i.e. not scrolling) so that the Draggable is moved relative to the page rather than moving with it, is that right?

I’m also curious out the method of opting out of native scrolling, and your thoughts on an approach like https://stackoverflow.com/a/17159809.

I also don’t know what the ideal state of starting a drag should be, whether it’s instant, force touch, touch and hold, or something else, and if you’re aiming to make the library opinionated and bake in support for one behaviour, or if you’re aiming to have it unopinionated and configurable.

@alexreardon
Copy link
Collaborator Author

@bradleyayers we cannot use native scrolling as the direction that a user moves an item to reorder it is often the different to the scroll direction for touch devices

  • reorder down: move finger down
  • scroll down: move finger up

Yes, we are opting out so the page stays fixed - at least until we support auto scrolling. The library will be opinionated - the idea is to interfere as little as possible with standard browser interactions while designing a drag and drop interaction that feels natural.

@alexreardon
Copy link
Collaborator Author

alexreardon commented Nov 10, 2017

These styles are now baked into DraggableStyle and NotDraggingStyle as they provide a sensible default and have no visual impact. Consumers are welcome to opt out or change these values (although I suspect that would never be the case)

// These styles are applied by default to allow for a
// better touch device drag and drop experience.
// Users can opt out of these styles or change them if they really need too
// for their specific use case.
type BaseStyle = {
  // A long press on anchors usually pops a content menu that has options for
  // the link such as 'Open in new tab'. Because long press is used to start
  // a drag we need to opt out of this behavior
  '-webkit-touch-callout': 'none',

  // Webkit based browsers add a grey overlay to anchors when they are active.
  // We remove this tap overlay as it is confusing for users
  // https://css-tricks.com/snippets/css/remove-gray-highlight-when-tapping-links-in-mobile-safari/
  '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',

  // Added to avoid the *pull to refresh action* and *anchor focus* on Android Chrome
  touchAction: 'none',
}

@bradleyayers
Copy link
Contributor

Thinking about this a bit more, I suppose https://stackoverflow.com/a/17159809 would only be useful if we wanted to go in the direction of "long press to lift draggable" UX. As in touch events would initially scroll the page, but if the user did not scroll beyond a small distance in our long-press-to-lift-duration, page scrolling would be "turned off" (by .preventDefault() on the touchmove events) and we'd lift the item and use the yet-to-be-implemented auto scrolling.

Basically it would be #1 but without the following, because the opt-out wouldn't happen up-front:

However, this can confuse the user as the interface can feel unresponsive as they are trying to native scroll but we have already opted out of native scrolling.

I think I'm still missing something though, because this isn't the approach you're going with. Is it:

  • The UX of long-press-to-lift isn't react-beautiful-dnd's goal.
  • There's a technical flaw with the approach that I missed (e.g. poor cross-browser support)
  • Something else

@alexreardon
Copy link
Collaborator Author

@bradleyayers it is a little hard to follow. Here is my attempt to explain it a little further:

  • When the user first puts their finger down we need to tell the browser whether to allow native scrolling or to block it. This is the only decision point and you cannot change your mind later.
  • When the user first puts their finger down we have no idea what they were trying to do (scroll, tap, drag)
  • We cannot have native scrolling while dragging. It is in the wrong direction
  • We must opt out of native scrolling as as it could be a drag
  • Because we have opted out of scrolling if the user trys to scroll nothing will happen - unless we start a drag on the movement

We now use the two following methods to check if a drag starts:

  1. a long press (hold for a small period of time - so we know it is not a tap) OR
  2. movement beyond a small threshold (so we know it is not a tap)

We also do a lot more work to ensure:

  • clicks are prevented when a drag has occurred
  • tapping can perform as normal
  • force pressing works

The decision was between whether we do nothing on a scroll or if we start a drag. Given that doing nothing sucks and feels broken we start the drag. The complication is the initial one time opt in / out decision.

Hopefully that provides some more information for you :)

@mostafah
Copy link

Another plus for the approach that you chose: it makes sense with custom drag handles too. When the items have drag handles in them, dragging by drag handle should always work and I’m guessing that scroll works by swiping outside of the drag handle. This is exactly what users expect.

@hakunin
Copy link

hakunin commented Nov 10, 2017

@alexreardon now the demo works without issues. It would be nice if the task could highlight when I tap it (even before I start moving) because now it feels like there is a lag in the phone's response because of the movement threashold.

@alexreardon
Copy link
Collaborator Author

You are welcome to patch the event handlers to add a selected state but it will not come out of the box with the library. Tap has particular meanings such as navigation that we do not want to overwrite (although you are welcome to)

@alexreardon
Copy link
Collaborator Author

Thanks everyone in your encouragement getting this across the line!

🤘

Closed by #165

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants