【问题标题】:How to bind 'touchstart' and 'click' events but not respond to both?如何绑定“touchstart”和“click”事件但不响应两者?
【发布时间】:2011-10-24 12:54:55
【问题描述】:

我正在开发一个必须在各种设备上运行的移动网站。目前让我头疼的是黑莓。

我们需要同时支持键盘点击和触摸事件。

理想情况下我会使用:

$thing.click(function(){...})

但我们遇到的问题是,这些黑莓设备中的一些设备从触摸到触发点击之间存在非常烦人的延迟。

补救办法是改用touchstart:

$thing.bind('touchstart', function(event){...})

但是我如何去绑定两个事件,但只触发一个?我仍然需要键盘设备的点击事件,但当然,如果我使用的是触摸设备,我不希望触发点击事件。

一个额外的问题:有没有办法做到这一点,并额外适应甚至没有触摸启动事件的浏览器?在对此进行研究时,似乎 BlackBerry OS5 不支持 touchstart,因此还需要依赖该浏览器的点击事件。

附录:

也许更全面的问题是:

使用 jQuery,是否可以/推荐使用相同的绑定处理触摸交互和鼠标交互?

理想情况下,答案是肯定的。如果没有,我确实有一些选择:

  1. 我们使用 WURFL 来获取设备信息,因此可以创建我们自己的设备矩阵。根据设备,我们将使用 touchstart 或 click。

  2. 通过 JS 检测浏览器中的触摸支持(我需要对此进行更多研究,但似乎可行)。

但是,这仍然存在一个问题:支持 BOTH 的设备怎么样。我们支持的一些手机(即诺基亚和黑莓)同时具有触摸屏键盘。所以这让我回到了最初的问题……有没有办法以某种方式同时允许两者?

【问题讨论】:

  • 你最好绑定到 touchstart 和 touchend 并在你的触摸逻辑旁边编写你自己的点击逻辑。内置点击回调不知道触摸。
  • 贾斯汀,我不确定我是否跟上了。难道我不会同时绑定一个 touchstart 和 click 事件吗?
  • @DA - 不,您根本不会绑定到 .click() 回调。我会尝试在一些 sudo 代码中写一个答案。我没有方便的触摸设备来编写真正的代码:)
  • 啊,但要澄清一下,我仍然需要点击事件,因为会有人使用非触摸设备访问此站点。
  • 使用.bind('touchstart mouseup') 将解决它(基于以下cmets之一)

标签: jquery click touchstart


【解决方案1】:

我发现的最佳方法是编写触摸事件并让该事件以编程方式调用正常的点击事件。这样您就拥有了所有正常的点击事件,然后您只需为所有触摸事件添加一个事件处理程序。对于您想要使其可触摸的每个节点,只需将“可触摸”类添加到它以调用触摸处理程序。使用 Jquery,它的工作原理就是这样,通过一些逻辑来确保它是真正的触摸事件而不是误报。

$("body").on("touchstart", ".touchable", function() { //make touchable  items fire like a click event
var d1 = new Date();
var n1 = d1.getTime();
setTimeout(function() {
    $(".touchable").on("touchend", function(event) {
        var d2 = new Date();
        var n2 = d2.getTime();
        if (n2 - n1 <= 300) {
            $(event.target).trigger("click"); //dont do the action here just call real click handler
        }
    });
}, 50)}).on("click", "#myelement", function() {
//all the behavior i originally wanted
});

【讨论】:

    【解决方案2】:

    查找文档滚动移动差异(水平和垂直)touchstart和touchend,如果其中一个大于1像素,则它是移动而不是单击

    var touchstartverscrollpos , touchstarthorscrollpos;
    
    
        $('body').on('touchstart','.thumbnail',function(e){
    
            touchstartverscrollpos = $(document).scrollTop();
            touchstarthorscrollpos = $(document).scrollLeft();
    
    
        });
    
    
    
        $('body').on('touchend','.thumbnail',function(e){
    
    
            var touchendverscrollpos = $(document).scrollTop();
            var touchendhorscrollpos = $(document).scrollLeft();
    
            var verdiff = touchendverscrollpos - touchstartverscrollpos;
            var hordiff = touchendhorscrollpos - touchstarthorscrollpos;
    
    
            if (Math.abs(verdiff) <1 && Math.abs(hordiff)<1){
    
    // do you own function () here 
    
    
    
                e.stopImmediatePropagation();
    
                return false;
            }
    
        });
    

    【讨论】:

      【解决方案3】:

      UDPATE:

      我一直在研究一种实现,以便为同一个函数同时使用 click 和 touchend 事件,如果类型发生变化,该函数会有效地阻止事件。我的目标是拥有一个响应速度更快的应用程序界面 - 我想减少事件启动到 UI 反馈循环的时间。

      为了使这个实现工作,假设您已在“点击”和“触摸结束”上添加了所有相关事件。这可以防止一个元素在两个事件都需要运行但类型不同时被剥夺事件冒泡。

      这是一个基于 API 的轻量级实现,我出于演示目的对其进行了简化。它演示了如何在折叠元素上使用该功能。

      var tv = {
          /**
           * @method eventValidator()
           * @desc responsible for validating event of the same type.
           * @param {Object} e - event object
           * @param {Object} element - element event cache
           * @param {Function} callback - callback to invoke for events of the same type origin
           * @param {Object} [context] - context to pass to callback function
           * @param {Array} [args] - arguments array to pass in with context. Requires context to be passed
           * @return {Object} - new event cache
           */
          eventValidator: function(e, element, callback, context, args){
              if(element && element.type && element.type !== e.type){
                  e.stopPropagation();
                  e.preventDefault();
                  return tv.createEventCacheObj({}, true);
              } else {
                  element = tv.createEventCacheObj(e);
                  (typeof context === "object" ? callback.apply(context, args) : callback());
                  return element;
              }
          },
      
          /**
           * @method createEventCacheObj()
           * @param {Object} event - event object
           * @param {String} [event.type] - event type
           * @param {Number} [event.timeStamp] - time of event in MS since load
           * @param {Boolean} [reset=false] - flag to reset the object
           * @returns {{type: *, time: string}}
           */
          createEventCacheObj: function (event, reset){
              if(typeof reset !== 'boolean') reset = false;
              return {
                  type: !reset ? event.type : null,
                  time: !reset ? (event.timeStamp).toFixed(2): null
              };
          }
      };
      
      // Here is where the magic happens
      var eventCache = [];
      var pos = 0;
      
      var $collapses = document.getElementsByClassName('tv-collapse__heading');
          Array.prototype.forEach.call($collapses, function(ele){
              ele.addEventListener('click', toggleCollapse);
              ele.addEventListener('touchend', toggleCollapse);
      
              // Cache mechanism
              ele.setAttribute('data-event-cache', String(pos++));
          });
      
      /**
       * @func toggleCollapse()
       * @param {Object} e - event object
       * @desc responsible for toggling the state of a collapse element
       */
      function toggleCollapse(e){
          eventCache[pos] = tv.eventValidator(e, eventCache[pos], function(){
             // Any event which isn't blocked will run the callback and its content
             // the context and arguments of the anonymous function match the event function context and arguments (assuming they are passed using the last two parameters of tv.eventValidator)
      
          }, this, arguments);
      }
      

      原答案:

      这是一个对 Rafael Fragoso's 答案的修改的响应 - 纯 JS。

      (function(){
        
        button = document.getElementById('sayHi');
        
        button.addEventListener('touchstart', ohHai);
        button.addEventListener('click', ohHai);
      
        function ohHai(event){
          event.stopPropagation();
          event.preventDefault();
          console.log('ohHai is:', event.type);
        };
      })();
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>SO - Answer</title>
      </head>
      <body>
        <button id="sayHi">Anyone there?</button>
      </body>
      </html>

      在下面运行sn-p并注意输出:

      • 电话
      • 平板电脑
      • 平板电脑(桌面模式 - 如果适用)
      • 桌面
      • 桌面(触摸屏 - 如果适用)

      关键是我们正在阻止连续的事件触发。当触摸发生时,移动浏览器会尽力模拟点击。我希望我能找到一篇文章的链接,该链接解释了在 touchstart 之后通过点击发生的所有事件。 (我正在寻找双击和点击实际触发之间的 300 毫秒延迟)。

      触控和鼠标设备

      我使用 Surface Pro 和带有触摸屏的 Windows 10 桌面进行了几次测试。我发现它们都触发了您所怀疑的事件,touchstart 用于触摸,click 用于触控板、鼠标和造型师。有趣的是,靠近但不在按钮上的触摸事件会在没有触摸事件的情况下触发点击事件。似乎 Windows 10 中的内置功能会查找半径内最近的节点,如果找到节点,它将触发基于鼠标的事件。

      同一类型的多个事件

      如果一个元素上有两个相同类型的事件,阻止事件冒泡可能会阻止其中一个事件触发。有几种不同的方法可以使用某种缓存来处理这个问题。我最初的想法是修改事件对象,但我们得到了一个参考,所以我认为缓存解决方案就足够了。

      【讨论】:

        【解决方案4】:

        我给出了答案there,我用jsfiddle 演示。 您可以检查不同的设备并进行报告。

        基本上,我使用一种事件锁和一些为其提供服务的功能:

        /*
         * Event lock functions
         * ====================
         */
        function getEventLock(evt, key){
           if(typeof(eventLock[key]) == 'undefined'){
              eventLock[key] = {};
              eventLock[key].primary = evt.type;
              return true;
           }
           if(evt.type == eventLock[key].primary)
              return true;
           else
              return false;
        }
        
        function primaryEventLock(evt, key){
           eventLock[key].primary = evt.type;
        }
        

        然后,在我的事件处理程序中,我从对我的锁的请求开始:

        /*
         * Event handlers
         * ==============
         */
        $("#add").on("touchstart mousedown", addStart);
        $("#add").on("touchend mouseup", addEnd);
        function addStart(evt){
           // race condition between 'mousedown' and 'touchstart'
           if(!getEventLock(evt, 'add'))
              return;
        
           // some logic
           now = new Date().getTime();
           press = -defaults.pressDelay;
           task();
        
           // enable event lock and(?) event repetition
           pids.add = setTimeout(closure, defaults.pressDelay);
        
           function closure(){
                // some logic(?): comment out to disable repetition
              task();
        
              // set primary input device
              primaryEventLock(evt, 'add');
        
              // enable event repetition
              pids.add = setTimeout(closure, defaults.pressDelay);
           }
        }
        function addEnd(evt){
              clearTimeout(pids.add);
        }
        

        我必须强调,问题不在于只对一个事件做出响应,而是对两者都做出响应。

        最后,在 jsfiddle 有一个更新版本的链接,我通过在事件开始和结束处理程序以及2 个作用域变量eventLockeventLockDelay

        【讨论】:

          【解决方案5】:

          如果您已经在使用 jQuery,那么有两种方法可以处理这个问题,它们都非常简短和实用。我认为它不需要像大多数答案那样复杂......

          1. 返回假;

          只需监听任何/所有事件并添加“return false;”最后停止额外的重复事件处理。

          thing$.on('click touchend',function(){
              // do Stuff
              return false; // stop
          });
          

          可重用示例:

          function bindClickAndTouchEvent(__targets$, __eventHandler){
              
              __targets$.on('click touchend',function(){
                  __eventHandler.apply(this,arguments);// send original event
                  return false;// but stop additional events
              });
          }
          
          // In-Line Usage:
          
          bindClickAndTouchEvents( $('#some-element-id'), function(){ 
              console.log('Hey look only one click even when using touch on a touchscreen laptop') 
          });
          
          1. 鼠标向上并触摸结束

          根据您的用例,您可能只使用 mouseup 和 touchend,因为这两个事件根本不重叠,并且只创建一个事件开始......然后您甚至不需要“return false;” .

          thing$.on('mouseup touchend',function(){
              // do Stuff
          });
          

          【讨论】:

            【解决方案6】:

            我不确定这是否适用于所有浏览器和设备。 我使用 Google Chrome 和 Safari iOS 对此进行了测试。

            $thing.on('click || touchend', function(e){
            
            });
            

            OR 操作数应该只触发第一个事件(在桌面上应该是 click 和 iPhone 上应该是 touchend)。

            【讨论】:

              【解决方案7】:

              您可以使用计数器来代替超时:

              var count = 0;
              $thing.bind('touchstart click', function(){
                count++;
                if (count %2 == 0) { //count 2% gives the remaining counts when devided by 2
                  // do something
                }
              
                return false
              });
              

              【讨论】:

              • % 返回两个操作数(var count 和 2)相除的余数
              • 计数器在某些情况下不会增加,即。移动$thing的手指后松开手指后。
              • 如果仅触发了 touchstart 或 click 之一,则此方法将不起作用。一键将计数器变为1,然后单击不会// do something
              猜你喜欢
              • 2012-11-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-04-25
              • 2012-08-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多