用触摸事件模拟移动设备上的拖放事件

4

不久前,我在移动设备上的Web浏览器中遇到了拖放问题。默认的JavaScript事件在移动设备上不起作用,只能使用触摸事件。

在我的情况下,我需要通过拖放来交换两个图像以及它们的ID。以下是一个示例:

div{
  display:inline-block;
  border:1px solid #0b79d0;
}

div, img{
        width:120px;
        height:120px;
    }
<div id="1" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
    <img id="a" draggable="true" ondragstart="dragStart(event)" src="https://static.webshopapp.com/shops/073933/files/156288269/345x345x1/artibalta-white-tiger.jpg"/>
</div>

<div id="2" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
    <img id="b" draggable="true" ondragstart="dragStart(event)" src="https://yt3.ggpht.com/a-/AN66SAyfsmao4f1EEOqkBP2PgpSUcabPJXLZ1sLEnA=s288-mo-c-c0xffffffff-rj-k-no"/>
</div>

<div id="3" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
    <img id="c" draggable="true" ondragstart="dragStart(event)" src="https://kinderbilder.download/wp-content/uploads/2018/10/animals-for-dolphin-drawings-pencil-drawings-pinterest-verwandt-mit-delfine-zeichnen-100x100.jpg"/>
</div>

<script>
    function allowDrop(ev){
        ev.preventDefault();
    }

    function dragEnter(ev){
        var element = document.getElementById(ev.target.id);
        element.style.border = "dotted";
        element.style.borderColor = "#0b79d0";
    }

    function dragLeave(ev){
        var element = document.getElementById(ev.target.id);
        element.style.border = "1px solid #0b79d0";
    }

    function dragStart(ev){
        ev.dataTransfer.setData("src", ev.target.id);
        var number = ev.target.id.replace ( /[^\d.]/g, '' );
        ev.dataTransfer.setData("text/plain", number);
    }

    function drop(ev) {
        ev.preventDefault();
        var src = document.getElementById(ev.dataTransfer.getData("src"));

        var srcParent = src.parentNode;
        var tgt = ev.currentTarget.firstElementChild;
        ev.currentTarget.replaceChild(src, tgt);
        srcParent.appendChild(tgt);

        var number1 = srcParent.id.replace(/[^\d.]/g, '');
        var number2 = ev.currentTarget.id.replace(/[^\d.]/g, '');

        var element = document.getElementById(ev.target.id);
        element.style.border = "solid 1px #0b79d0";

        var number = ev.target.id.replace(/[^\d.]/g, '');
    }
</script>

dragStart事件会存储一些信息,例如图片。但是,使用触摸事件无法执行此操作。

现在我想知道,在移动设备上是否有一种方法可以通过使用触摸事件来模拟拖动事件并实现相同的功能呢?

4个回答

1

对于新手来说,目前似乎没有适用于触摸事件的dataTransfer(还没有?)。您可以通过更多的工作实现与DnD相同的功能。 DnD只是简化了数据传输过程并处理了一些东西,例如检测dragenter,但您可以使用触摸实现相同的效果,只是需要自己完成所有的dragenter检测工作。

在触摸开始时,我将拖动元素的引用存储到变量中,这类似于dataTransfer.setData(),但是需要添加的工作是通过复制新元素来模拟拖放的感觉以跟随您的触摸事件。

  function dragTouchstart(e){
//global variable that store the touch/drag element
  imgId = e.target
    let image = document.createElement("img"); // Create a new element
  image.setAttribute("id", "image-float");


  // get the image from the stored reference
  image.src =imgId.src
  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.body.appendChild(image);  
  }

在 touchmove 事件中,这仅仅是为了模拟从 dnd 中得到的拖动感觉,获取在 touchstart 中创建的元素并使其跟随你的 touchmove。但我还添加了 touchenter 函数来检测 dragenter。
   function dragTouchmove(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';
  let touchX = e.touches[0].pageX
  let touchY = e.touches[0].pageY
  //apply touch enter fucntion inside touch move
  dragTouchenter(e,touchX,touchY)
  }

我的touchenter函数

  function dragTouchenter(e,touchX,touchY){
  let one =document.getElementById('1')
  let two =document.getElementById('2')
  let three =document.getElementById('3')
  
  let id1 = one.getBoundingClientRect();
  let id2 = two.getBoundingClientRect();
  let id3 = three.getBoundingClientRect();
  
    // to detect the overlap of touchmove with dropzone
  var overlap1 = !(id1.right < touchX ||
    id1.left > touchX ||
    id1.bottom < touchY ||
    id1.top > touchY)
    
  var overlap2 = !(id2.right < touchX ||
    id2.left > touchX ||
    id2.bottom < touchY ||
    id2.top > touchY)
    
     var overlap3 = !(id3.right < touchX ||
    id3.left > touchX ||
    id3.bottom < touchY ||
    id3.top > touchY)
    
    //detect touchenter then apply style, if false, equal to touchleave
    //could write a better function to take these elements in array and apply function accordingly
    //but as for now I just write like this because faster by copy+paste
    //get dropZoneId too
    if(overlap1){
     one.style.border = "dotted";
    one.style.borderColor = "#0b79d0";
    dropZoneId =one
    }else{
    one.style.border = "1px solid #0b79d0";
    }
    
    if(overlap2){
     two.style.border = "dotted";
    two.style.borderColor = "#0b79d0";    
    dropZoneId =two
    }else{
    two.style.border = "1px solid #0b79d0";
    }
    
    if(overlap3){
     three.style.border = "dotted";
    three.style.borderColor = "#0b79d0";
    dropZoneId =three
    }else{
    three.style.border = "1px solid #0b79d0";
    }
    
    if(!overlap1 && !overlap2 && !overlap3){
    dropZoneId = ''
    }
    
    /* console.log(dropZoneId.id) */
  }

最后,ontouchend将执行您使用dataTransfer.getData()的所有逻辑

  function dragTouchend(e){
  //remove dragged image duplicate
    let image = document.getElementById('image-float')
    image.remove()
    
    dropZoneId.style.border = "1px solid #0b79d0";
    //if outside any dropzone, just do nothing
    if(dropZoneId == '') {
    dropZoneId = ''
    imgId = ''
    }else{
    // if inside dropzone, swap the image 
    let toSwap = dropZoneId.children[0]
    let originDropzone= imgId.parentElement
    originDropzone.appendChild(toSwap)
    dropZoneId.appendChild(imgId)
    
    dropZoneId = ''
    imgId = ''
    
    }
  }

下面是一个基于 OP 的完整工作示例,更改了一个图像,因为它似乎已经损坏了。

div {
  display: inline-block;
  border: 1px solid #0b79d0;
}

div,
img {
  width: 120px;
  height: 120px;
}
<div id="1" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
  <img id="a" draggable="true" ondragstart="dragStart(event)" src="https://static.webshopapp.com/shops/073933/files/156288269/345x345x1/artibalta-white-tiger.jpg" ontouchstart="dragTouchstart(event)" ontouchmove="dragTouchmove(event)" ontouchend="dragTouchend(event)"
  />
</div>

<div id="2" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
  <img id="b" draggable="true" ondragstart="dragStart(event)" src="https://yt3.ggpht.com/a-/AN66SAyfsmao4f1EEOqkBP2PgpSUcabPJXLZ1sLEnA=s288-mo-c-c0xffffffff-rj-k-no" ontouchstart="dragTouchstart(event)" ontouchmove="dragTouchmove(event)" ontouchend="dragTouchend(event)"
  />
</div>

<div id="3" ondragover="allowDrop(event)" ondrop="drop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)" draggable="false">
  <img id="c" draggable="true" ondragstart="dragStart(event)" src="https://cdn.quasar.dev/img/avatar1.jpg" ontouchstart="dragTouchstart(event)" ontouchmove="dragTouchmove(event)" ontouchend="dragTouchend(event)" />
</div>

<script>
  var imgId = 'test'
  var dropZoneId = ''

  function allowDrop(ev) {
    ev.preventDefault();
  }

  function dragTouchstart(e) {
    imgId = e.target
    let image = document.createElement("img"); // Create a new element
    image.setAttribute("id", "image-float");


    // get the image from the stored reference
    image.src = imgId.src
    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.body.appendChild(image);
  }

  function dragTouchmove(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';
    let touchX = e.touches[0].pageX
    let touchY = e.touches[0].pageY
    //apply touch enter fucntion inside touch move
    dragTouchenter(e, touchX, touchY)
  }

  function dragTouchenter(e, touchX, touchY) {
    let one = document.getElementById('1')
    let two = document.getElementById('2')
    let three = document.getElementById('3')

    let id1 = one.getBoundingClientRect();
    let id2 = two.getBoundingClientRect();
    let id3 = three.getBoundingClientRect();

    // to detect the overlap of touchmove with dropzone
    var overlap1 = !(id1.right < touchX ||
      id1.left > touchX ||
      id1.bottom < touchY ||
      id1.top > touchY)

    var overlap2 = !(id2.right < touchX ||
      id2.left > touchX ||
      id2.bottom < touchY ||
      id2.top > touchY)

    var overlap3 = !(id3.right < touchX ||
      id3.left > touchX ||
      id3.bottom < touchY ||
      id3.top > touchY)

    //detect touchenter then apply style, if false, equal to touchleave
    //could write a better function to take these elements in array and apply function accordingly
    //but as for now I just write like this because faster by copy+paste
    //get dropZoneId too
    if (overlap1) {
      one.style.border = "dotted";
      one.style.borderColor = "#0b79d0";
      dropZoneId = one
    } else {
      one.style.border = "1px solid #0b79d0";
    }

    if (overlap2) {
      two.style.border = "dotted";
      two.style.borderColor = "#0b79d0";
      dropZoneId = two
    } else {
      two.style.border = "1px solid #0b79d0";
    }

    if (overlap3) {
      three.style.border = "dotted";
      three.style.borderColor = "#0b79d0";
      dropZoneId = three
    } else {
      three.style.border = "1px solid #0b79d0";
    }

    if (!overlap1 && !overlap2 && !overlap3) {
      dropZoneId = ''
    }

    /* console.log(dropZoneId.id) */
  }

  function dragTouchend(e) {
    //remove dragged image duplicate
    let image = document.getElementById('image-float')
    image.remove()

    dropZoneId.style.border = "1px solid #0b79d0";
    //if outside any dropzone, just do nothing
    if (dropZoneId == '') {
      dropZoneId = ''
      imgId = ''
    } else {
      // if inside dropzone, swap the image 
      let toSwap = dropZoneId.children[0]
      let originDropzone = imgId.parentElement
      originDropzone.appendChild(toSwap)
      dropZoneId.appendChild(imgId)

      dropZoneId = ''
      imgId = ''

    }
  }

  function dragEnter(ev) {
    var element = document.getElementById(ev.target.id);
    element.style.border = "dotted";
    element.style.borderColor = "#0b79d0";
  }

  function dragLeave(ev) {
    var element = document.getElementById(ev.target.id);
    element.style.border = "1px solid #0b79d0";
  }

  function dragStart(ev) {
    ev.dataTransfer.setData("src", ev.target.id);
    var number = ev.target.id.replace(/[^\d.]/g, '');
    ev.dataTransfer.setData("text/plain", number);
  }

  function drop(ev) {
    ev.preventDefault();
    var src = document.getElementById(ev.dataTransfer.getData("src"));

    var srcParent = src.parentNode;
    var tgt = ev.currentTarget.firstElementChild;
    ev.currentTarget.replaceChild(src, tgt);
    srcParent.appendChild(tgt);

    var number1 = srcParent.id.replace(/[^\d.]/g, '');
    var number2 = ev.currentTarget.id.replace(/[^\d.]/g, '');

    var element = document.getElementById(ev.target.id);
    element.style.border = "solid 1px #0b79d0";

    var number = ev.target.id.replace(/[^\d.]/g, '');
  }
</script>


Safari 在 iOS 上有 bug。 - chovy
@chovy,可以告诉我问题出在哪里吗? - aleng
在iOS上它只是到处闪烁。 - chovy
@chovy 这很奇怪...是一直这样吗?我在Safari iOS(SE)和iPadOS上测试,无法重现。嗯,也许只是Safari的问题。 - aleng
刚刚使用了你的内联演示。 - chovy

0
我强烈建议您在移动端使用库来实现这一功能。 我曾经需要实现类似的行为,我使用了interact.js,这个库非常方便且适用于移动设备。
如果您只想向应用程序添加触摸手势,请查看Hammer.js。这是使用触摸事件的主要库。
如果您使用jQuery,请查看此主题
希望这可以帮助到您。

-1
  • 解决这个问题的方法(如果时间是主要因素)是实现 touch punch - http://touchpunch.furf.com/

  • 这是一个 jquery-ui 插件,将触摸事件转换为鼠标事件(通常只需导入库即可,无需更改代码)


-2

从教育目的来看,你所做的很好。

但是对于开发而言,最好不要重复造轮子,而是能够使用工具,这样可以帮助你节省时间。

因此,我推荐你使用这个拖放库。易于使用、有趣、轻量级且维护得非常好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接