2

In react I have an element that I want to drag and drop. Right now I have a dragStart and dragEnd that uses ev.dataTransfer.setData(data) to drag and drop my element to a different box.

However, I also want to be able to do this on a touch screen. When I use a touchscreen, the dragStart doesn't do anything. I figured out instead that I should use TouchStart and TouchEnd for the touchscreen. However, there is no dataTransfer event with touchStart. I'm wondering what the equivalent would be?

If there is no equivalent, what would be a good way for me to drag and drop my elements on touchscreen?

Thanks

AAAAAAA
  • 21
  • 2

1 Answers1

1

Reactjs is abit alien to me, but generally in vanilla, dataTransfer for touch can be done by storing your data into object/variable. Basically that's all dataTransfer do while you drag an element.

To do this, ontouchstart event, store the data that you want to carry like you did with dataTransfer.setData(), then simulate the dragging of an element by duplicating it.

  touchstart(e, item, index, arr) {
    this.state.touch['item'] = item
    this.state.touch['index'] = index
    this.state.touch['arr'] = arr
    let image = document.createElement("img"); // Create a new element
    image.setAttribute("id", "image-float");


    // get the image from the stored reference
    image.src = 'https://cdn.quasar.dev/img/avatar' + item + '.jpg';
    image.width = 100
    image.height = 100

    // position the image to the touch, can be improve to detect the position of touch inside the image
    let left = e.touches[0].pageX;
    let top = e.touches[0].pageY;
    image.style.position = 'absolute'
    image.style.left = left + 'px';
    image.style.top = top + 'px';
    image.style.opacity = 0.5;
    document.getElementById('app').appendChild(image);
  }

then in ontouchmove event, you can have the duplicated element to follow cursor, to simulate the dragging effect. while on touchmove event, we can detect either our cursor is inside the dropzone element, like you would normally have as onDrop event for DnD. this is the manual part that DnD has helped to smoothen the experience, but for touch, we need to do the detection by ourself.

  touchmove(e) {
    let image = document.getElementById('image-float')
    // this will give us the dragging feeling of the element while actually it's a different element
    let left = e.touches[0].pageX;
    let top = e.touches[0].pageY;
    image.style.position = 'absolute'
    image.style.left = left + 'px';
    image.style.top = top + 'px';

    let topDrop = this.getDropzone(e, left, top, 'top')
    let botDrop = this.getDropzone(e, left, top, 'bottom')

    if (!topDrop && !botDrop) this.state.touch.drop = null
    if (topDrop) this.state.touch.drop = 'top'
    if (botDrop) this.state.touch.drop = 'bottom'
  }

  getDropzone(e, left, top, id) {
    let rect1 = document.getElementById(id).getBoundingClientRect();
    // to detect the overlap of mouse into the dropzone, as alternative of mouseover
    var overlap = !(rect1.right < left ||
      rect1.left > left ||
      rect1.bottom < top ||
      rect1.top > top)

    return overlap

  }

finally, in our ontouchend event, we do the logic as we have for our dataTransfer.getData(). Because we already detect if our cursor is within the dropzone or not, so we can perform the logic accordingly

  touchend() {    
    let image = document.getElementById('image-float')
    image.remove()
    let item = this.state.touch.item
    let index = this.state.touch.index
    let arr = this.state[this.state.touch.arr]
    let drop = this.state[this.state.touch.drop]

    /*   console.log(this.state.touch.arr) */
    if (this.state.touch.drop != null) {
      if (drop != arr) {
        drop.push(item)
        arr.splice(index, 1)
      }
      return this.setState({
        state: {
          drop: this.state[drop],
          arr: this.state[arr]
        }
      })
    }
  }

Below, is my attempt on having this delivered in Reactjs

class TodoApp extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      top: [
        '2',
        '3',
        '4',
      ],
      bottom: [],
      touch: {}
    }
  }

  dragstart(e, item, index, arr) {
    e.dataTransfer.dropEffect = 'move'
    e.dataTransfer.effectAllowed = 'move'
    e.dataTransfer.setData('item', item)
    e.dataTransfer.setData('index', index)
    e.dataTransfer.setData('arr', arr)
  }
  drop(e, drop) {
    let item = e.dataTransfer.getData('item')
    let index = e.dataTransfer.getData('index')
    let arr = e.dataTransfer.getData('arr')

    if (drop != item.arr) {
      this.state[drop].push(item)
      this.state[arr].splice(index, 1)
    }
    return this.setState({
      state: {
        drop: this.state[drop],
        arr: this.state[arr]
      }
    })
  }

  touchstart(e, item, index, arr) {
    this.state.touch['item'] = item
    this.state.touch['index'] = index
    this.state.touch['arr'] = arr
    let image = document.createElement("img"); // Create a new element
    image.setAttribute("id", "image-float");


    // get the image from the stored reference
    image.src = 'https://cdn.quasar.dev/img/avatar' + item + '.jpg';
    image.width = 100
    image.height = 100

    // position the image to the touch, can be improve to detect the position of touch inside the image
    let left = e.touches[0].pageX;
    let top = e.touches[0].pageY;
    image.style.position = 'absolute'
    image.style.left = left + 'px';
    image.style.top = top + 'px';
    image.style.opacity = 0.5;
    document.getElementById('app').appendChild(image);
  }

  touchmove(e) {
    let image = document.getElementById('image-float')
    // this will give us the dragging feeling of the element while actually it's a different element
    let left = e.touches[0].pageX;
    let top = e.touches[0].pageY;
    image.style.position = 'absolute'
    image.style.left = left + 'px';
    image.style.top = top + 'px';

    let topDrop = this.getDropzone(e, left, top, 'top')
    let botDrop = this.getDropzone(e, left, top, 'bottom')

    if (!topDrop && !botDrop) this.state.touch.drop = null
    if (topDrop) this.state.touch.drop = 'top'
    if (botDrop) this.state.touch.drop = 'bottom'
  }

  getDropzone(e, left, top, id) {
    let rect1 = document.getElementById(id).getBoundingClientRect();
    // to detect the overlap of mouse into the dropzone, as alternative of mouseover
    var overlap = !(rect1.right < left ||
      rect1.left > left ||
      rect1.bottom < top ||
      rect1.top > top)

    return overlap

  }

  touchend() {

    let image = document.getElementById('image-float')
    image.remove()
    let item = this.state.touch.item
    let index = this.state.touch.index
    let arr = this.state[this.state.touch.arr]
    let drop = this.state[this.state.touch.drop]

    /*   console.log(this.state.touch.arr) */
    if (this.state.touch.drop != null) {
      if (drop != arr) {
        drop.push(item)
        arr.splice(index, 1)
      }
      return this.setState({
        state: {
          drop: this.state[drop],
          arr: this.state[arr]
        }
      })
    }
  }


  render() {
    return ( <
      div className = "container" >
      <
      div id = "top"
      className = "dropzone"
      onDrop = {
        (event) => this.drop(event, 'top')
      }
      onDragOver = {
        (e) => {
          e.preventDefault()
        }
      }
      onDragEnter = {
        (e) => {
          e.preventDefault()
        }
      } > {
        this.state.top.map((item, i) => {
          return ( < img src = {
              'https://cdn.quasar.dev/img/avatar' + item + '.jpg'
            }
            width = "100"
            height = "100"
            key = {
              i
            }
            onDragStart = {
              (event) => this.dragstart(event, item, i, 'top')
            }
            onTouchStart = {
              (event) => this.touchstart(event, item, i, 'top')
            }
            onTouchMove = {
              (event) => this.touchmove(event)
            }
            onTouchEnd = {
              (event) => this.touchend()
            }
            draggable = 'true' /
            >
          )
        })
      } < /div>

      <
      div id = "bottom"
      className = "dropzone"
      onDrop = {
        (event) => this.drop(event, 'bottom')
      }
      onDragOver = {
        (e) => {
          e.preventDefault()
        }
      }
      onDragEnter = {
        (e) => {
          e.preventDefault()
        }
      } > {
        this.state.bottom.map((item, i) => {
          return ( < img src = {
              'https://cdn.quasar.dev/img/avatar' + item + '.jpg'
            }
            width = "100"
            height = "100"
            key = {
              i
            }
            onDragStart = {
              (event) => this.dragstart(event, item, i, 'bottom')
            }
            onDragStart = {
              (event) => this.dragstart(event, item, i, 'bottom')
            }
            onTouchStart = {
              (event) => this.touchstart(event, item, i, 'bottom')
            }
            onTouchMove = {
              (event) => this.touchmove(event)
            }
            onTouchEnd = {
              (event) => this.touchend()
            }
            draggable = 'true' /
            >
          )
        })
      } < /div>  </div >
    )
  }
}

ReactDOM.render( < TodoApp / > , document.getElementById("app"))
body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

button {
  color: #4fc08d;
}

button {
  background: none;
  border: solid 1px;
  border-radius: 2em;
  font: inherit;
  padding: 0.75em 2em;
}

.dropzone {
  display: flex;
  height: fit-content;
  min-width: 50px;
  min-height: 50px;
  background: #2D2D2D;
  margin: 10px;
  padding: 10px;
}

.dropzone>* {
  margin: 0 5px;
}

.container {
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app"></div>
aleng
  • 366
  • 2
  • 13
  • @JulioSpinelli Great it works for you in Android, but sorry that I can't help much with Surface as I don't have one, but I think this might help https://stackoverflow.com/a/13237682/13176134 – aleng Mar 20 '22 at 16:49