首页 > 解决方案 > 触摸屏上的 HTML 拖放

问题描述

我正在处理一项任务,该任务涉及拖动图像并检查它的放置位置并在将其放置在正确位置时执行操作。虽然它在任何有鼠标的东西上都可以正常工作,但它不能在触摸屏上工作。如何在触摸屏上实现这一目标。使用 Vuejs 2 或 vanilla javascript

拖动项目

<v-row v-for="(item, iterator) in Activity.drag_items" :key="item.class" :class="[item.class, item.status]" class="drag-item">
   <v-img 
      draggable
      @dragstart='startDrag($event, item, iterator)'
      :src="require(`@/assets/img/activities/activity_2/${item.item_img}`)"
      contain
      :class="item.status"
   ></v-img>
</v-row>

掉落物品

<a @drop='onDrop($event, Activity)' @dragover.prevent @dragenter.prevent></a>

拖拽功能

startDrag(evt, item, index){
        evt.dataTransfer.dropEffect = 'move';
        evt.dataTransfer.effectAllowed = 'move';
        evt.dataTransfer.setData('item', JSON.stringify(item));
        evt.dataTransfer.setData('index', index);
    }

落下功能

onDrop(evt, galaxy_location) {}

标签: javascriptvue.jsdrag-and-droptouchtouchmove

解决方案


就目前而言,触摸事件还没有dataTransfer对象。一种方法是使用复制值或数据并根据触摸事件对其进行变异的方法。在我的示例中,我有三种方法来模拟触摸拖放

在触摸启动时,我将所需的引用存储到对象中,这类似于 dataTransfer.setData(),但这里添加的工作是通过在 touchstart 上删除项目并复制一个新元素来模拟拖放的感觉触摸事件

  touchstartDrag(e, item, arr) {
  // go through origin array
  arr.forEach((el, i) => {
    if (el == item) {
      // store it as reference
      this.touchDragItem = {
        item: item,
        index: i,
        arr: arr
      }
      // remove item in the array, or you can change opacity
      arr.splice(i, 1)
    }
  })
  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${this.touchDragItem.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';


  document.getElementById('app').appendChild(image);
},

在 touchmove 上,这纯粹是为了模拟你从 dnd 得到的拖动感觉,获取在 touchstart 中创建的元素并使其跟随你的 touchmove

    touchmoveDrag(e) {

  // on touch move or dragging, we get the newly created image element
  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';
  this.touchX = e.touches[0].pageX
  this.touchY = e.touches[0].pageY

},

在您定义放置功能的触摸端。因为没有 drop 事件,你必须根据 dropzone 手动检测你的触摸,如果它在 dropzone 之外,定义你的逻辑,如果它在里面,按照你定义 dataTransfer.getData() 相应地执行它

    touchendDrag(e) {
  // remove the image on touch end
  let image = document.getElementById('image-float')
  image.remove()
  // get the dropzone of top and bottom
  let rect1 = document.getElementById('top').getBoundingClientRect();
  let rect2 = document.getElementById('bottom').getBoundingClientRect()
  // to detect the overlap of mouse into the dropzone, as alternative of mouseover
  var overlapTop = !(rect1.right < this.touchX ||
    rect1.left > this.touchX ||
    rect1.bottom < this.touchY ||
    rect1.top > this.touchY)
  // to detect the overlap of mouse into the dropzone bottom
  var overlapBottom = !(rect2.right < this.touchX ||
    rect2.left > this.touchX ||
    rect2.bottom < this.touchY ||
    rect2.top > this.touchY)
  // get the stored reference
  let ex = this.touchDragItem
  // if on touchend the touch is not inside any dropzone, just restore back to the original array
  if (!overlapTop && !overlapBottom) {
    ex.arr.splice(ex.index, 0, ex.item)
  } else {
    if (overlapTop) {
      if (this.top == ex.arr) {
        ex.arr.splice(ex.index, 0, ex.item)
      }
      if (this.top != ex.arr) {
        this.top.push(ex.item)

      }


    }
    if (overlapBottom) {
      if (this.bottom == ex.arr) {
        ex.arr.splice(ex.index, 0, ex.item)
      }
      if (this.bottom != ex.arr) {
        this.bottom.push(ex.item)

      }
    }
  }
  this.touchDragItem = null

},

总的来说,这是一种模拟触摸事件的 dnd API 的幼稚方法,有很多 vue.js 拖放库供您使用,具体取决于您的用例,例如sortable.js。但是如果你想实现自己的触摸拖动,这是你可以开始的地方

这是完整的工作示例

new Vue({
  el: "#app",
  data: {
    touchDragItem: null,
    touchX: null,
    touchY: null,
    top: ['1', '2', '3'],
    bottom: [],
  },
  methods: {
    dragmouse(e, item) {
      e.dataTransfer.dropEffect = 'move'
      e.dataTransfer.effectAllowed = 'move'
      e.dataTransfer.setData('item', item)
    },
    onDrop(e, pos) {
      let arr = pos == 'top' ? 'bottom' : 'top'
      let item = e.dataTransfer.getData('item')
      this[arr].forEach((el, i) => {
        if (el == item) {
          this[arr].splice(i, 1)
          this[pos].push(el)
        }
      })
    },


    touchstartDrag(e, item, arr) {
      // go through origin array
      arr.forEach((el, i) => {
        if (el == item) {
          // store it as reference
          this.touchDragItem = {
            item: item,
            index: i,
            arr: arr
          }
          // remove item in the array, or you can change opacity
          arr.splice(i, 1)
        }
      })
      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${this.touchDragItem.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';


      document.getElementById('app').appendChild(image);
    },


    touchmoveDrag(e) {

      // on touch move or dragging, we get the newly created image element
      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';
      this.touchX = e.touches[0].pageX
      this.touchY = e.touches[0].pageY

    },
    touchendDrag(e) {
      // remove the image on touch end
      let image = document.getElementById('image-float')
      image.remove()
      // get the dropzone of top and bottom
      let rect1 = document.getElementById('top').getBoundingClientRect();
      let rect2 = document.getElementById('bottom').getBoundingClientRect()
      // to detect the overlap of mouse into the dropzone, as alternative of mouseover
      var overlapTop = !(rect1.right < this.touchX ||
        rect1.left > this.touchX ||
        rect1.bottom < this.touchY ||
        rect1.top > this.touchY)
      // to detect the overlap of mouse into the dropzone bottom
      var overlapBottom = !(rect2.right < this.touchX ||
        rect2.left > this.touchX ||
        rect2.bottom < this.touchY ||
        rect2.top > this.touchY)
      // get the stored reference
      let ex = this.touchDragItem
      // if on touchend the touch is not inside any dropzone, just restore back to the original array
      if (!overlapTop && !overlapBottom) {
        ex.arr.splice(ex.index, 0, ex.item)
      } else {
        if (overlapTop) {
          if (this.top == ex.arr) {
            ex.arr.splice(ex.index, 0, ex.item)
          }
          if (this.top != ex.arr) {
            this.top.push(ex.item)

          }


        }
        if (overlapBottom) {
          if (this.bottom == ex.arr) {
            ex.arr.splice(ex.index, 0, ex.item)
          }
          if (this.bottom != ex.arr) {
            this.bottom.push(ex.item)

          }
        }
      }
      this.touchDragItem = null

    },
  }
})
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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <div id="top" class="dropzone" @drop="onDrop($event,'top')" @dragenter.prevent @dragover.prevent>
    <img ref="image" draggable @dragstart="dragmouse($event,item)" @touchstart.prevent="touchstartDrag($event,item,top)" @touchmove="touchmoveDrag" @touchend="touchendDrag" :src="`https://cdn.quasar.dev/img/avatar${item}.jpg`" width="100" height="100" v-for="item in top"
      :key="item" />
  </div>


  <div id="bottom" class="dropzone" @drop="onDrop($event,'bottom')" @dragenter.prevent @dragover.prevent>
    <img ref="image" draggable @dragstart="dragmouse($event,item)" @touchstart.prevent="touchstartDrag($event,item,bottom)" @touchmove="touchmoveDrag" @touchend="touchendDrag" :src="`https://cdn.quasar.dev/img/avatar${item}.jpg`" width="100" height="100"
      v-for="item in bottom" :key="item" />
  </div>
</div>


推荐阅读