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>