interact.js is a Javascript library that simplifies and unifies all sorts of mouse and touch-related events. It mostly keeps its paws out of the DOM in favor of exposing events. In other words, if you want to implement drag-and-drop, you’re expected to listen to interactjs’s events and update the DOM yourself. This makes it a great fit for React!

Suppose we want to do this:

drag

Here’s the React component:

Draggable = React.createClass
  getInitialState: ->
    x: 0
    y: 0

  componentDidMount: ->
    interact(@getDOMNode())
      .draggable
        onmove: @_onMove

  render: ->
    `(
      <div style={ this._style() } />
    )`

  _style: ->
    backgroundColor: "green"
    width: 50
    height: 50
    transform: "translate(#{@state.x}px, #{@state.y}px)"

  _onMove: (e) ->
    @setState
      x: @state.x + e.dx
      y: @state.y + e.dy

In componentDidMount we tell interact.js to fire drag events for the component’s DOM node. The _onMove method is called during the course of a drag, be it with a mouse or touch events.

We use the deltas (dx and dy) from the move handler’s event object to adjust the element’s position, and we render the change using a CSS transform.

Reverting the Drag

Providing an animated revert like the following requires a few extra steps:

drag-revert

We can use a CSS transition translating back to (0, 0) along with a 0.25s duration to achieve the effect. We can do this by adding two new event handlers:

  • onstart: set the duration to 0 so that there is no lag during regular dragging.
  • onend: set x and y to 0 to revert to the element’s original position, and the duration to 0.25s to animate it.

Full source code:

Draggable = React.createClass
  getInitialState: ->
    x: 0
    y: 0

  componentDidMount: ->
    interact(@getDOMNode())
      .draggable
        onstart: @_onStart
        onmove: @_onMove
        onend: @_onEnd

  render: ->
    `(
      <div style={ this._style() } />
    )`

  _style: ->
    backgroundColor: "green"
    width: 50
    height: 50
    transform: "translate(#{@state.x}px, #{@state.y}px)"
    transitionDuration: @state.duration

  _touchStart: ->
    @setState(x: 150, y: 150)

  _onStart: (e) ->
    @setState
      duration: 0

  _onMove: (e) ->
    @setState
      x: @state.x + e.dx
      y: @state.y + e.dy

  _onEnd: (e) ->
    @setState(x: 0, y: 0, duration: "0.25s")

Webkit Vendor Prefixes

You’ll need to use the -webkit- vendor prefix for the transform property for this to work in Safari, iOS and Android.