【问题标题】:prevent touchstart when swiping滑动时防止触摸启动
【发布时间】:2011-10-27 12:30:44
【问题描述】:

我在移动设备上有一个可滚动列表。他们希望人们能够通过滑动来滚动列表,并通过点击来选择一行。

关键在于将两者结合起来。如果您实际上正在滚动列表,我不希望选择一行。这是我发现的:

滚动时不触发:

  • 点击
  • 上鼠标

滚动时触发:

  • 鼠标按下
  • 触摸启动
  • 触摸结束

简单的解决方案是坚持点击事件。但我们发现,在某些黑莓设备上,touchstart 与触发 click 或 mouseup 之间存在非常明显的延迟。这种延迟非常严重,以至于无法在这些设备上使用。

所以这给我们留下了其他选择。但是,使用这些选项,您可以滚动列表,而无需触发您触摸的行开始滚动。

解决此问题的最佳做法是什么?

【问题讨论】:

  • 我有同样的问题,我已经为你的问题添加了赏金。
  • 遇到了一些相同的问题,最终构建了原生。
  • 我会写一个答案。我们最终所做的是在 touchstart 处记录位置,然后在 touchend 处记录位置。如果它们有显着不同,我们什么也不做,因为我们假设意图是滚动。如果它们很接近,那么我们会选中该框,假设它是一个水龙头。为了仍然允许点击事件(键盘需要),我们将在 touchstart 上添加一个类,它会暂时阻止任何点击事件。
  • 你能举个例子说明如何用 jQuery 做到这一点吗?
  • 你在吗,伙计?尽可能回复。

标签: javascript jquery scroll touch touch-event


【解决方案1】:

我想出了这个,因为我想要一个也可以防止动态链接的全局事件。 所以preventDefault()必须在事件监听器中是可调用的,为此触摸事件被引用。

检测与迄今为止在此处发布的大多数解决方案一样。在触摸结束时检查元素是否仍然相同或者我们是否移动了一些量。

$('li a').on('ontouchstart' in document.documentElement === true ? 'touchClick' : 'click', handleClick);

if('ontouchstart' in document.documentElement === true) {
    var clickedEl = null;
    var touchClickEvent = $.Event('touchClick');
    var validClick = false;
    var pos = null;
    var moveTolerance = 15;
    $(window).on('touchstart', function(e) {
        /* only handle touch with one finger */
        if(e.changedTouches.length === 1) {
            clickedEl = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
            pos = {x: e.touches[0].clientX, y: e.touches[0].clientY};
            validClick = true;
        } else {
            validClick = false;
        }
    }).on("touchmove", function(e) {
        var currentEl = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
        if(
            e.changedTouches.length > 1 ||
            (!$(clickedEl).is(currentEl) && !$.contains(clickedEl, currentEl)) ||
            (Math.abs(pos.y - e.touches[0].clientY) > moveTolerance && Math.abs(pos.x - e.touches[0].clientX) > moveTolerance)
        ) {
            validClick = false;
        }
    }).on("touchend", function(e) {
        if(validClick) {
            /* this allowes calling of preventDefault on the touch chain */
            touchClickEvent.originalEvent = e;
            /* target is used in jQuery event delegation */
            touchClickEvent.target = e.target;
            $(clickedEl).trigger(touchClickEvent);
        }
    });
}

【讨论】:

    【解决方案2】:
    var touchmoved;
    $('button').on('touchend', function(e){
        if(touchmoved != true){
            // button click action
        }
    }).on('touchmove', function(e){
        touchmoved = true;
    }).on('touchstart', function(){
        touchmoved = false;
    });
    

    【讨论】:

    • 这样做会以某种方式取消我的滚动操作。
    • 不错!事件的顺序——或者更好的说法是 touchmoved 变量的赋值非常重要。如果你把它们换掉它就行不通了(至少在 vanilla JS 上不行)
    • 谢谢。我是 5 小时试图找到这个解决方案!
    【解决方案3】:

    如果您想对多个元素执行此操作并且还需要鼠标和指针事件:

    var elems = $('YOURMULTISELECTOR'); // selector for multiple elements
    elems.unbind('mousdown pointerdown touchstart touchmove mouseup pointerup touchend');
    var elem = null;
    elems.on('mousdown pointerdown touchstart', function (e) {
        elem = yourSingleSelector(e);
    }).on('touchmove', function (e) {
        elem = null;                
    }).on('mouseup pointerup touchend', function (e) { 
        if (elem == yourSingleSelector(e)) {                    
            // do something
        }
    });
    

    【讨论】:

      【解决方案4】:

      我使用这段代码,以便按钮只有在没有被滑动时才会触发(在触摸端):

      var startY;
      var yDistance;
      
      function touchHandler(event) {
          touch = event.changedTouches[0];
          event.preventDefault();
      }
      
      $('.button').on("touchstart", touchHandler, true);
      $('.button').on("touchmove", touchHandler, true);
      
      $('.button').on("touchstart", function(){
          startY = touch.clientY;
      });
      
      $('.button').on('touchend', function(){
      
          yDistance = startY - touch.clientY;
      
          if(Math.abs(yDist) < 30){
      
              //button response here, only if user is not swiping
              console.log("button pressed")
          }
      });
      

      【讨论】:

        【解决方案5】:

        我做了一些不同的工作。它绝对不是很优雅,当然也不适合大多数情况,但它对我有用。

        我一直在使用 jQuery 的 toggleSlide() 来打开和关闭输入 div,在 touchstart 上触发幻灯片。问题是当用户想要滚动时,触摸的 div 会打开。为了阻止这种情况发生(或在用户注意到之前将其反转),我在文档中添加了一个 touchslide 事件,该事件将关闭最后一次触摸的 div。

        更深入一点,这里有一段代码sn-p:

        var lastTouched;
        
        document.addEventListener('touchmove',function(){
            lastTouched.hide();
        });
        
        $('#button').addEventListener('touchstart',function(){
            $('#slide').slideToggle();
            lastTouched = $('#slide');
        });
        

        全局变量存储上次触摸的 div,如果用户滑动,document.touchmove 事件会隐藏该 div。有时你会看到一个 div 闪烁,但它可以满足我的需要,而且很简单,我可以想出。

        【讨论】:

          【解决方案6】:

          我遇到了这个优雅的解决方案,它就像使用 jQuery 的魅力一样。我的问题是阻止列表项在滚动期间调用它们的触摸开始事件。这也适用于滑动。

          1. 使用“listObject”类将 touchstart 绑定到将滚动或滑动的每个项目

            $('.listObject').live('touchstart', touchScroll);
            
          2. 然后为每个项目分配一个数据对象属性,定义要调用的函数

            <button class='listObject' data-object=alert('You are alerted !')>Alert Me</button>
            

          以下函数将有效区分点击和滚动或滑动。

          function touchScroll(e){
          
              var objTarget = $(event.target);
          
              if(objTarget.attr('data-object')){
                  var fn = objTarget.attr('data-object'); //function to call if tapped    
              }   
          
              if(!touchEnabled){// default if not touch device
                  eval(fn);
                  console.log("clicked", 1);
                  return;
              }
          
              $(e.target).on('touchend', function(e){
                  eval(fn); //trigger the function
                  console.log("touchEnd")      
                  $(e.target).off('touchend');
              });
          
              $(e.target).on('touchmove', function(e){
                  $(e.target).off('touchend');
                  console.log("moved")
              }); 
          
          }
          

          【讨论】:

          • 嗯,太糟糕了,这并不能消除库存 android 浏览器上的幽灵点击,否则这个解决方案将是黄金。需要进行更多实验...
          【解决方案7】:

          jQuery Mobile 有一个 .tap() 事件,它似乎具有您所期望的行为:

          jQuery Mobile 点击事件在单个目标对象上发生的快速、完整的触摸事件之后触发。它相当于标准点击事件的手势,在触摸手势的释放状态下触发。

          这可能不一定能回答问题,但可能是一些有用的替代方法。

          【讨论】:

            【解决方案8】:

            其中一些解决方案对我有用,但最后我发现这个轻量级库更易于设置。

            Tocca.js:https://github.com/GianlucaGuarini/Tocca.js

            它非常灵活,可以检测触摸以及滑动、双击等。

            【讨论】:

            • 同意。我有许多元素必须在移动设备中单击两次而不是一次,因此我将所有“单击”事件替换为“点击”事件,现在桌面和移动设备的工作方式相同。 :)
            【解决方案9】:

            我遇到了同样的问题,这里有一个适合我的快速解决方案

            $(document).on('touchstart', 'button', function(evt){ 
                var oldScrollTop = $(window).scrollTop();
                window.setTimeout( function() {
                    var newScrollTop = $(window).scrollTop();
                    if (Math.abs(oldScrollTop-newScrollTop)<3) $button.addClass('touchactive');
                }, 200);
            });
            

            基本上不是立即处理touchstart,而是等待几毫秒(本例中为200ms),然后检查滚动位置,如果滚动位置发生了变化,那么我们不需要处理touchstart。

            【讨论】:

              【解决方案10】:

              引用 DA.:

              这是一个工作示例:

              var touch_pos;
              $(document).on('touchstart', '.action-feature', function(e) {
                e.preventDefault();
                touch_pos = $(window).scrollTop();
              }).on('click touchend', '.action-feature', function(e) {
                e.preventDefault();
                if(e.type=='touchend' && (Math.abs(touch_pos-$(window).scrollTop())>3)) return;
                alert("only accessed when it's a click or not a swipe");
              });
              

              【讨论】:

              • 对我的用例进行了一些修改,这对我来说非常有效。非常感谢!
              【解决方案11】:

              这就是我最终想出的方法,它允许通过滑动滚动项目列表,而且每个项目都可以通过点击“触发”。此外,您仍然可以使用键盘(使用 onclick)。

              我认为这类似于 Netlight_Digital_Media 的回答。我需要再研究一下。

              $(document)
              // log the position of the touchstart interaction
              .bind('touchstart', function(e){ 
                touchStartPos = $(window).scrollTop();
              })
              // log the position of the touchend interaction
              .bind('touchend', function(e){
                // calculate how far the page has moved between
                // touchstart and end. 
                var distance = touchStartPos - $(window).scrollTop();
              
                var $clickableItem; // the item I want to be clickable if it's NOT a swipe
              
                // adding this class for devices that
                // will trigger a click event after
                // the touchend event finishes. This 
                // tells the click event that we've 
                // already done things so don't repeat
              
                $clickableItem.addClass("touched");      
              
                if (distance > 20 || distance < -20){
                      // the distance was more than 20px
                      // so we're assuming they intended
                      // to swipe to scroll the list and
                      // not selecting a row. 
                  } else {
                      // we'll assume it was a tap 
                      whateverFunctionYouWantToTriggerOnTapOrClick()
                  }
              });
              
              
              $($clickableItem).live('click',function(e){
               // for any non-touch device, we need 
               // to still apply a click event
               // but we'll first check to see
               // if there was a previous touch
               // event by checking for the class
               // that was left by the touch event.
              if ($(this).hasClass("touched")){
                // this item's event was already triggered via touch
                // so we won't call the function and reset this for
                // the next touch by removing the class
                $(this).removeClass("touched");
              } else {
                // there wasn't a touch event. We're
                // instead using a mouse or keyboard
                whateverFunctionYouWantToTriggerOnTapOrClick()
              }
              });
              

              【讨论】:

              • 嗯,当我点击一行时,我的 iPhone 记录了一个错误,提示 TypeError: 'undefined' is not an object。我用alert('tapped') 替换了whateverFunctionYouWantToTriggerOnTapOrClick(),但它没有被调用。
              • 是的,这不是工作代码...您需要根据需要放入自己的逻辑。只是一个例子来展示所使用的逻辑。
              • lolwut: 你必须获取你的行并将它们分配给变量$clickableItem,否则当你调用addClassclickableItem 将是undefined它。像这样:var clickableItem = $("#row")[0];
              • 我在这里看到的另一个问题是 .live() 的使用,它在 jQuery v1.7 中已被弃用并在 1.9 中被删除。请改用 .on()。
              • 我在我的项目中测试了这种方法。唯一的问题是,如果用户向上滚动,然后向下滚动(几乎到第一个位置),就会被认为是点击。
              【解决方案12】:

              您基本上想要做的是检测什么是滑动,什么是点击。

              我们可能会设置一些条件:

              1. 滑动是指触摸点p1,然后将手指移至p2,同时手指仍位于屏幕上,然后松开。
              2. 点击是指您在同一元素上点击开始点击和结束点击。

              因此,如果您存储 touchStart 发生位置的坐标,则可以在 touchEnd 处测量差异。如果变化足够大,则将其视为滑动,否则,将其视为单击。

              另外,如果你想把它做得非常整洁,你还可以在touchMove 期间检测你用手指“悬停”在哪个元素上,如果你还没有在你开始的元素上点击,您可以运行clickCancel 方法来移除高光等。

              // grab an element which you can click just as an example
              var clickable = document.getElementById("clickableItem"),
              // set up some variables that we need for later
              currentElement,
              clickedElement;
              
              // set up touchStart event handler
              var onTouchStart = function(e) {
                  // store which element we're currently clicking on
                  clickedElement = this;
                  // listen to when the user moves finger
                  this.addEventListener("touchMove" onTouchMove);
                  // add listener to when touch end occurs
                  this.addEventListener("touchEnd", onTouchEnd);
              };
              // when the user swipes, update element positions to swipe
              var onTouchMove = function(e) {
                  // ... do your scrolling here
              
                  // store current element
                  currentElement = document.elementFromPoint(x, y);
                  // if the current element is no longer the same as we clicked from the beginning, remove highlight
                  if(clickedElement !== currentElement) {
                      removeHighlight(clickedElement);
                  }
              };
              // this is what is executed when the user stops the movement
              var onTouchEnd = function(e) {
                  if(clickedElement === currentElement) {
                      removeHighlight(clickedElement);
                      // .... execute click action
                  }
              
                  // clean up event listeners
                  this.removeEventListener("touchMove" onTouchMove);
                  this.removeEventListener("touchEnd", onTouchEnd);
              };
              function addHighlight(element) {
                  element.className = "highlighted";
              }
              function removeHighlight(element) {
                  element.className = "";
              }
              clickable.addEventListener("touchStart", onTouchStart);
              

              然后,您还必须为可滚动元素添加侦听器,但您不必担心如果手指在 touchStarttouchEnd 之间移动会发生什么。

              var scrollable = document.getElementById("scrollableItem");
              
              // set up touchStart event handler
              var onTouchStartScrollable = function(e) {
                  // listen to when the user moves finger
                  this.addEventListener("touchMove" onTouchMoveScrollable);
                  // add listener to when touch end occurs
                  this.addEventListener("touchEnd", onTouchEndScrollable);
              };
              // when the user swipes, update element positions to swipe
              var onTouchMoveScrollable = function(e) {
                  // ... do your scrolling here
              };
              // this is what is executed when the user stops the movement
              var onTouchEndScrollable = function(e) {
                  // clean up event listeners
                  this.removeEventListener("touchMove" onTouchMoveScrollable);
                  this.removeEventListener("touchEnd", onTouchEndScrollable);
              };
              scrollable.addEventListener("touchStart", onTouchStartScrollable);
              

              // 西蒙 A.

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2014-01-04
                • 2017-02-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-15
                • 2021-01-18
                相关资源
                最近更新 更多