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

Posted

技术标签:

【中文标题】使用移动设备的触摸事件模拟拖放事件【英文标题】:Imitating Drag and Drop Events with Touch Events for Mobile Devices 【发布时间】:2019-05-01 00:20:14 【问题描述】:

前段时间,我在移动设备上的网络浏览器中进行拖放操作。默认的 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 事件存储信息,例如图像。但是,触摸事件无法进行此类操作。

现在我想知道,有没有办法在移动设备上做同样的事情,通过使用触摸事件来模仿拖动事件?

【问题讨论】:

【参考方案1】:

我强烈建议您在移动设备中使用库来执行此操作。 我不得不做相对相同的行为,我使用了interact.js,它非常少并且可以移动。

如果您只想为您的应用添加触摸手势,请查看Hammer.js。它是使用触摸事件的主要库。

如果您使用 jQuery,请查看 topic。

我希望这会有所帮助。

【讨论】:

【参考方案2】:

对于新来者,目前还没有用于触摸事件的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>

【讨论】:

ios Safari 中的越野车 @chovy 我可以知道是什么问题吗? 在 ios 上它只是到处闪烁。 @chovy 这很奇怪.. 是否一致?我在 safari ios(SE) 上测试过,iPadOS 无法重现。 welp,也许这只是野生动物园的事情? 只需使用您的内联演示。【参考方案3】:

出于教育目的,您正在做的事情很好。

但对于开发而言,最好不要重新发明***并能够使用工具,随着时间的推移可以帮助您。

所以我向你推荐这个Drag & Drop 库。易于使用、有趣、轻便且维护良好。

【讨论】:

【参考方案4】:

前段时间解决它的方法(如果时间是主要因素)是实施触摸打孔 - http://touchpunch.furf.com/

它是 jquery-ui 插件,将触摸事件连接到鼠标事件(因此通常导入 lib 就足够了 - 无需更改代码)

【讨论】:

以上是关于使用移动设备的触摸事件模拟拖放事件的主要内容,如果未能解决你的问题,请参考以下文章

移动设备触摸事件的触发次数不如我点击的次数多

在移动设备上使用可排序的触摸事件 + jquery-ui 无法按预期工作

JavaScript 切换桌面/移动设备的悬停/触摸事件

javascript 事件处理

Chrome DevTools 触摸仿真不起作用

如何在移动设备上手动触发 mouseleave 事件