React-dnd: Touch backend

Created on 27 Oct 2014  ·  25Comments  ·  Source: react-dnd/react-dnd

It'd be killer if there was a touch backend that would be switched-in automatically for touch devices.

enhancement

Most helpful comment

For those who come after me, here seems to be the path forward https://github.com/LouisBrunner/react-dnd-multi-backend

All 25 comments

Indeed! I'll look into this next week.

I did something like this using pointer-events: none; so the elements under the item being dragged could catch mouse over events; i'm interested in seeing what other ways there are to tackle this

What are state-of-the-art drag and drop libraries using touchmove? jQuery UI? We need to look at a good solution and adopt its tricks.

I think it would be best and in the spirit of this library to avoid any DOM manipulation (like popular libs do, whether by cloning node for preview, changing its translateX/Y, etc), and instead let the consumer draw "drag layer" and give consumer necessary data for this.

In other words if Card component is dragged with touch backend, react-dnd won't try to somehow move it itself. It's up to you to render some "layer" component at the top of your app. This component will use DragLayerMixin provided by react-dnd to learn about current element, its position, etc. It decides how to render the dragged item based by its type and is 100% React as well.

var App = React.createClass({
  render() {
    return (
      <div>
       {this.props.activeRouteHandler />
       <DragDropLayer />
      </div>
    );
  }
});

var { DragLayerMixin } = require('react-dnd');

var DragDropLayer = React.createClass({
  mixins: [DragLayerMixin],

  render() {

    // With non-native dragging backend, rendering items in the process of dragging is your responsibility. 
    // It's up to you to use translate3d or top/left, render thumbnails or real components, etc.

    var {
      draggedItem,
      draggedItemType,
      clientX,
      clientY
    } = this.getDragLayerState(); // provided by mixin

    if (!draggedItem) {
      return null;
    }

    return (
      <div style={{zIndex: 10000, pointerEvents: 'none', position: 'absolute'}}>
        {draggedItem &&
          <div style={{transform: `translate3d(${clientX}, ${clientY}, 0)`}}>
            {this.renderItem(draggedItemType, draggedItem)}
          </div>
        }
      </div>
    );
  },

  renderItem(type, item) {

    // It's completely up to the consumer to specify how to render different types

    switch (type) {
    case ItemTypes.Card:
      return <Card title={item.title} />;
    case ItemTypes.List:
      return <img src='thumbnail.jpg' />;
  }
});

BTW this approach opens up really cool possibilities because _for some apps_ drag layer _is_ what contains draggable items. Drag layer doesn't _have to be_ separate, although it can, if neccessary.

For apps where items are dragged upon some container, this container can use DragLayerMixin and thus learn about current drag position and move real elements as drag occurs:

screen shot 2014-10-28 at 20 30 15

For apps such as Trello where items are dragged across the screen across different containers, it can be useful to have a separate top-level component using DragLayerMixin to render absolutely positioned currently dragged item.

This makes a lot of sense. Almost any browser, whether desktop or mobile, should be able to smoothly animate an item around the screen. This would make it easy then to add cool 3d effects like Trello does when moving a card.

This would also allow setting the mouse cursor while dragging and more flexible placeholder rendering... Things you can't really do with the native DND api

I just realized we can _also_ make this "drag layer" API available for browser D&D API. Just set dragImage to a transparent pixel like in this example and draw whatever you want.

@nelix @KyleAMathews

I have a proof of concept here: http://gaearon.github.io/react-dnd/#/drag-around-experimental

It will blink while you move it :-) (won't work in Firefox yet)

Relevant source code:

https://github.com/gaearon/react-dnd/blob/experiments/examples/_drag-around-experimental/index.js#L14

https://github.com/gaearon/react-dnd/blob/experiments/examples/_drag-around-experimental/DragLayer.js

https://github.com/gaearon/react-dnd/blob/experiments/examples/_drag-around-experimental/Box.js#L69

As you can see, it's up to the consumer to draw drag layer and components on it, so it's really flexible.

I'll try to finish this up this week as a stepping stone to touch support, and after that it shouldn't be difficult to do the same for touch mode.

We use a drag layer much like this, but we use onMouseMove etc so that you can change the mouse cursor...
A minor but very important concern for us. It also allows use to use mostly the same code for touch and pointer events.
Still the DragLayer stuff is a step in the right direction for me, once you do touch support I'd be willing to try and extend that to support pointer and mouse events if you are interested in having those as features?

I'd like to have HTML5 D&D API as the default backend and then make touch-based/mousemove-based backends opt-in.

HTML5 D&D would be the simplest one (no need for drag layer, everything managed by browser). Opting in to mousemove/touchmove ones would require you to implement a drag layer to draw whatever you like.

Makes sense?

Sure does, thats what I hoped would happen.
HTML5 D&D is fast and (besides the quirks) pretty simple to use so its the obvious choice by default.

So I just had a request to make this drag and drop touch enabled. Has anyone done this and want to share tips?

@Georgette

@nelix started work on mousemove backend, we'll do touchmove backend after that.

You can look at work in progress here: https://github.com/nelix/react-dnd/commit/8de7f7fe24c7ae397a971c517dada9323e6c27f0 (it's still far from being done and doesn't really work at this point). We still need to figure out which parts stay in DragDropMixin and which need to be moved into backends/HTML5 (and other backends).

If you'd like to work on that, you can start with that commit and keep implementing mousemove backend. When it works, it shouldn't be hard to implement a similar touchmove backend.

Please, if you'd like to work on this, keep us updated here or in Gitter room, so we don't end up with two implementations.

Here's a proof of concept of touch support: https://github.com/gaearon/react-dnd/pull/143

You can play with it: http://rawgit.com/gaearon/react-dnd/touch-poc/examples/index.html

It uses HTML5 backend on desktop and touch backend on mobile.
Only custom rendering example will show a drag preview on mobile.

This seems quite promising! Any ideas on when this might land? :)

I'm currently busy with another project and will be until July.
I might be able to work on this somewhere in July or August, but I'll be happy if somebody beats me to it :-)

since 1.1 was mentioned in this thread and v1.1.4 is out. I'm just wondering if the proof of concept above is already included in 1.1.4?

Nope.

I shouldn't have used a precise version number cause you never know when you're going to bump :-)

I currently don't have time to work on this but the proof of concept is in the PR. I'll probably update the PR to reflect the current API and wait for somebody to actually implement it.

Let's continue the discussion in https://github.com/gaearon/react-dnd/pull/240.

For those who come after me, here seems to be the path forward https://github.com/LouisBrunner/react-dnd-multi-backend

I wrote this snippet:

function multiBackends(...backendFactories) {
  return function(manager) {
    const backends = backendFactories.map(b => b(manager));
    return {
      setup: (...args) =>
          backends.forEach(b => b.setup.apply(b, args)),
      teardown: (...args) =>
          backends.forEach(b => b.teardown.apply(b, args)),
      connectDropTarget: (...args) =>
          backends.forEach(b => b.connectDropTarget.apply(b, args)),
      connectDragPreview: (...args) =>
          backends.forEach(b => b.connectDragPreview.apply(b, args)),
      connectDragSource: (...args) =>
          backends.forEach(b => b.connectDragSource.apply(b, args)),
    };
  };
}

Which I use like this:

DragDropContext(multiBackends(
      ReactDnDHTML5Backend,
      ReactDnDTouchBackend,
  ))

react-dnd-html5-backend and react-dnd-touch-backend listen to a disjoint set of events. (dragstart, dragend, dragenter, dragleave dragover, and drop vs touchstart, touchend and touchmove).

Some basic testing and it works fine, my drag and drop now supports touch and mouse events at the same time.

Are there any reasons it wouldn't work?

Was this page helpful?
0 / 5 - 0 ratings