【问题标题】:Drag/Reposition Element On touchmove not working拖动/重新定位元素在 touchmove 上不起作用
【发布时间】:2020-11-18 22:30:34
【问题描述】:

我的屏幕上有一个固定窗口,我希望用户能够在屏幕上拖动它。虽然我已将它设置为适用于鼠标输入,但我很难让它适用于触摸屏。

所以我有一个像这样的基本盒子:

<div id="smallavatar" class="smallbox">
</div>

紧随其后的是这个JS(注意前半部分是针对点击事件的)

var divOverlay = document.getElementById ("smallavatar");
var isDown = false;
divOverlay.addEventListener('mousedown', function(e) {
    isDown = true;
}, true);

document.addEventListener('mouseup', function() {
  isDown = false;
}, true);

document.addEventListener('mousemove', function(event) {
   event.preventDefault();
   if (isDown) {
   var deltaX = event.movementX;
   var deltaY = event.movementY;
  var rect = divOverlay.getBoundingClientRect();
  divOverlay.style.left = rect.x + deltaX + 'px';
  divOverlay.style.top  = rect.y + deltaY + 'px';
 }
}, true);

divOverlay.addEventListener('touchstart', e => {
  clientX = e.touches[0].screenX;
  clientY = e.touches[0].screenY;
});

divOverlay.addEventListener('touchmove', e => {
  let x =  divOverlay.style.left;
  let y =  divOverlay.style.top;
  // Compute the change in X and Y coordinates. 
  // The first touch point in the changedTouches
  // list is the touch point that was just removed from the surface.
  
  deltaX = e.changedTouches[0].clientX - clientX;
  deltaY = e.changedTouches[0].clientY - clientY;
  
  divOverlay.style.left = divOverlay.style.left + deltaX;
  divOverlay.style.top = divOverlay.style.top + deltaY;
    
});

最后使用了以下 CSS:

.smallbox{position:fixed; bottom:70px; right:0; width:360px;  height:400px; z-index:20;}

现在虽然 touchmove 事件确实有效,但问题是盒子似乎很快就飞离了屏幕。这个想法应该是我试图在触摸事件开始之前获取 div 使用的位置,然后计算出在 touchmove 事件中发生了多少移动,然后用新的值更新 left 和 top 值它应该在的位置。

有什么错误可以解释为什么这不能按预期工作吗?

【问题讨论】:

    标签: javascript html jquery css touch-event


    【解决方案1】:

    您的代码中存在一个导致意外结果的主要缺陷:

    • touchstart,您存储clientXclientY,而在touchmove 期间,您继续在deltaX = e.changedTouches[0].clientX - clientX;(和Y)引用这两个值。
      但是,虽然e.changedTouches[0].clientX 不断更新,clientX 却永远不会更新并始终保持其初始起始值。
      因此,对于下一帧/迭代,deltaX = e.changedTouches[0].clientX - clientX; 实际上将是最后一帧的 delta-x,加上前一帧的 delta-x,以及前一帧和前一帧的 delta-x,依此类推,直到第一帧。
      因此,您的 divOverlay 将呈指数级移动,并在几分之一秒内消失。

    • 获得divOverlay的当前位置的最佳选择是什么,取决于你的情况,虽然我认为在大多数情况下,使用pageX/pageY可能是你最安全的选择。
      其他选项为clientX/clientYscreenX/screenY,请参阅thisthis article 了解三者之间的区别。
      无论如何,除非绝对必要,否则我不建议在一次计算中互换使用它们。

    查看下面的现场 sn-p 以获得解决方案:

    var divOverlay = document.getElementById("smallavatar");
    
    /*MOUSE-LISTENERS---------------------------------------------*/
    var isDown=false;
    divOverlay.addEventListener('mousedown',function(){isDown=true;},true);
    document.addEventListener('mouseup',function(){isDown=false;},true);
    document.addEventListener('mousemove',function(e) {
      e.preventDefault();
      if (isDown) {
        divOverlay.style.left = divOverlay.offsetLeft + e.movementX + 'px';
        divOverlay.style.top  = divOverlay.offsetTop + e.movementY + 'px';
      }
    },true);
    
    /*TOUCH-LISTENERS---------------------------------------------*/
    var startX=0, startY=0;
    divOverlay.addEventListener('touchstart',function(e) {
      startX = e.changedTouches[0].pageX;
      startY = e.changedTouches[0].pageY;
    });
    
    divOverlay.addEventListener('touchmove',function(e) {
      e.preventDefault();
      var deltaX = e.changedTouches[0].pageX - startX;
      var deltaY = e.changedTouches[0].pageY - startY;
      divOverlay.style.left = divOverlay.offsetLeft + deltaX + 'px';
      divOverlay.style.top = divOverlay.offsetTop + deltaY + 'px';
      //reset start-position for next frame/iteration
      startX = e.changedTouches[0].pageX;
      startY = e.changedTouches[0].pageY;
    });
    .smallbox{position:fixed; bottom:70px; right:0; width:36px; height:40px; z-index:20; background:red;}
    &lt;div id="smallavatar" class="smallbox"&gt;&lt;/div&gt;
    jsfiddle:https://jsfiddle.net/2hdczkwn/4/
    • 为了解决您的主要缺陷,我将startX = e.changedTouches[0].pageX;startY = e.changedTouches[0].pageY; 添加到您的touchmove 侦听器的末尾,将每个新帧/迭代的开始位置重置为最后一帧/迭代的结束位置.
    • 我还将e.preventDefault(); 添加到您的touchmove 侦听器中,否则任何可滚动页面也会在您拖动divOverlay 时开始滚动。
    • 我将您的箭头函数转换为传统的箭头函数,例如鼠标监听器。
    • 我在函数之外添加了var startX=0, startY=0;,以确保它们是global vars
    • 我在 mousemove 监听器中替换了 var rect = divOverlay.getBoundingClientRect();rect.xrect.y,在 touchmove 监听器中替换了 divOverlay.style.leftdivOverlay.style.top,用 @ 987654359@ 和 divOverlay.offsetTop.
      这实际上非常重要,因为 divOverlay.style.left 返回一个 px 值,例如57px。在计算中使用此值时,例如57px + 12,它不会导致6969px,而是NaN

    我不禁注意到你的鼠标和触摸监听器是用不同的编码风格编写的,mouse 使用传统的 JS,而 touch 是用新的ES6,使用letarrow functions
    就好像您从其他人那里复制了触摸侦听器并且没有更改它以匹配您的其余代码一样。

    虽然可以使用任何一种样式并使用其他人的代码(只要您有权限),但我不鼓励在同一个项目中同时使用它们,而且简单地复制其他人的代码绝不是一个好主意无需重写以适合您自己的风格。
    这将确保您的整个项目保持其当前的编码风格,并具有让您真正了解您正在使用的代码的额外优势。

    【讨论】:

    • @DigM 我已经更新了我的解决方案,所以它确实有效:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 2020-07-02
    • 1970-01-01
    相关资源
    最近更新 更多