【问题标题】:Detect left/right-swipe on touch-devices, but allow up/down-scrolling检测触摸设备上的左/右滑动,但允许向上/向下滚动
【发布时间】:2013-07-10 09:41:56
【问题描述】:

我需要检测左/右滑动并做出反应,但希望用户能够滚动相同的元素,因此只要他以最大的上/下移动仅左/右移动手指X像素,它不应该滚动,但是当他超过X时,它应该滚动。

所以我做的是:

var startX, startY, $this = $(this);
function touchmove(event) {
        var touches = event.originalEvent.touches;
        if (touches && touches.length) {
            var deltaX = touches[0].pageX - startX;
            var deltaY = touches[0].pageY - startY;
            if (Math.abs(deltaY) > 50) {
                $this.html('X: ' + deltaX + '<br> Y: ' + deltaY + '<br>TRUE');
                $this.unbind('touchmove', touchmove);
                return true;
            } else {
                $this.html('X: ' + deltaX + '<br> Y: ' + deltaY);
                event.preventDefault();
            }
        }
    }

    function touchstart(event) {
        var touches = event.originalEvent.touches;
        if (touches && touches.length) {
            startX = touches[0].pageX;
            startY = touches[0].pageY;
            $this.bind('touchmove', touchmove);
        }
        //event.preventDefault();
    }

但我没有恢复在“if”情况下滚动的能力......

感谢任何提示。

【问题讨论】:

  • 感谢@T.J.Crowder,添加了它
  • @Raphael:你在任何地方声明它们吗?
  • 是的,将其添加到顶部 @T.J.Crowder

标签: javascript jquery touch swipe


【解决方案1】:

我编写了自己的触摸处理程序事件。也许这对你有帮助

它检查:

快速点击:'fc'

向左滑动:'swl'

向右滑动:'swr'

向上滑动:'swu'

向下滑动:'swd'

每个检查都会初始化它的对应事件。但是您可以滚动并执行您通常执行的任何其他操作。你只是有一些新的事件。

你需要 swl swr,我也建议使用 fc (fastclick) 来处理点击事件……它比普通点击要快得多。

window.onload = function() {
    (function(d) {
        var
            ce = function(e, n) {
                var a = document.createEvent("CustomEvent");
                a.initCustomEvent(n, true, true, e.target);
                e.target.dispatchEvent(a);
                a = null;
                return false
            },
            nm = true,
            sp = {
                x: 0,
                y: 0
            },
            ep = {
                x: 0,
                y: 0
            },
            touch = {
                touchstart: function(e) {
                    sp = {
                        x: e.touches[0].pageX,
                        y: e.touches[0].pageY
                    }
                },
                touchmove: function(e) {
                    nm = false;
                    ep = {
                        x: e.touches[0].pageX,
                        y: e.touches[0].pageY
                    }
                },
                touchend: function(e) {
                    if (nm) {
                        ce(e, 'fc')
                    } else {
                        var x = ep.x - sp.x,
                            xr = Math.abs(x),
                            y = ep.y - sp.y,
                            yr = Math.abs(y);
                        if (Math.max(xr, yr) > 20) {
                            ce(e, (xr > yr ? (x < 0 ? 'swl' : 'swr') : (y < 0 ? 'swu' : 'swd')))
                        }
                    };
                    nm = true
                },
                touchcancel: function(e) {
                    nm = false
                }
            };
        for (var a in touch) {
            d.addEventListener(a, touch[a], false);
        }
    })(document);
    //EXAMPLE OF USE
    var h = function(e) {
        console.log(e.type, e)
    };
    document.body.addEventListener('fc', h, false); // 0-50ms vs 500ms with normal click
    document.body.addEventListener('swl', h, false);
    document.body.addEventListener('swr', h, false);
    document.body.addEventListener('swu', h, false);
    document.body.addEventListener('swd', h, false);
}

在这种情况下,h 是我处理每种类型事件的处理程序,我将处理程序添加到正文中。

对于我理解你的问题,你只需要写下

YOURELEMENT.addEventListener('swr',YOURSWIPERIGHTFUNCTION,false);
YOURELEMENT.addEventListener('swl',YOURSWIPELEFTFUNCTION,false);

要处理多个元素和相同的功能...只需添加一个处理程序。

如果你有

<ul id="ul"><li>1</li><li>2</li><li>3</li></ul>

你会的:

var deleteli=function(e){
    var li=e.target;
    console.log('deleting '+li.textContent);
}
document.getElementById('ul').addEventListener('swl',deleteli,false);

fc 和 swr 相同

ios有一个bug:不要使用alert() ..它会执行2次。

【讨论】:

  • 不幸的是,这对我来说太复杂了:/另一个解决方案是使用jgestures.codeplex.com,您可以在左滑、右滑等上使用事件侦听器
  • 它是专门为低cpu设备设计的,因为它可以防止许多检查和计算,并且只使用你看到的短代码而不需要添加第三方库,也不会覆盖原生滚动系统设备,这可能使我的解决方案不那么滞后。如果您使用坐标 x、y 向上、向下、向左或向右移动,或者您没有移动(在 touchstart 和 touchend 上的坐标相同),则函数本身会检查 touchend .在每种情况下,它都会创建一个新的自定义事件(方向或点击,如果未移动)。所有计算都在触摸端完成
  • 这是你发布的一个很棒的小 sn-p,cocco,非常感谢!我确实遇到了 Android Chrome 的一个问题:如果您不阻止Default(),Chrome 几乎会在 touchmove 事件开始后立即触发 touchcancel。为了在不破坏原生 touchmove 功能的情况下解决此问题,我检查了 x 和 y 轴上的遍历距离。为此,请将代码中的 touchmove 更新为:touchmove:function(e){nm=false;ep={x:e.touches[0].pageX,y:e.touches[0].pageY};if(Math.abs(ep.x - sp.x) &gt; 10 &amp;&amp; Math.abs(ep.y - sp.y) &lt; 20) e.preventDefault();} 根据需要调整距离
  • touchmove 函数不应该有复杂的计算,以防止旧设备上的性能损失。如果删除整个 touchcancel 函数会发生什么?我没有安卓。
【解决方案2】:

接受的答案中有一个“错误”。如果您在 Android 上不使用 Chrome,而是使用内置浏览器或“webview”(例如,对于 html5-hybrid-app),则不会检测到滑动。

我发现由于正常的滚动行为,该事件不会触发。所以添加“e.preventDefault();”在 touchmove 中将修复它或 Eric Fuller 在接受的答案中的修复。

这是一个很好的截图,但在移动 Web 应用程序或网站中,这可能会导致糟糕的滚动卡顿,因为始终观察到触摸事件。

所以我决定构建一些新的东西。它不像拥有新的事件侦听器那样舒服,但它足以满足我的需求并且它的性能很好。

function detectswipe(el,func) {
  swipe_det = new Object();
  swipe_det.sX = 0;
  swipe_det.sY = 0;
  swipe_det.eX = 0;
  swipe_det.eY = 0;
  var min_x = 20;  //min x swipe for horizontal swipe
  var max_x = 40;  //max x difference for vertical swipe
  var min_y = 40;  //min y swipe for vertical swipe
  var max_y = 50;  //max y difference for horizontal swipe
  var direc = "";
  ele = document.getElementById(el);
  ele.addEventListener('touchstart',function(e){
    var t = e.touches[0];
    swipe_det.sX = t.screenX; 
    swipe_det.sY = t.screenY;
  },false);
  ele.addEventListener('touchmove',function(e){
    e.preventDefault();
    var t = e.touches[0];
    swipe_det.eX = t.screenX; 
    swipe_det.eY = t.screenY;    
  },false);
  ele.addEventListener('touchend',function(e){
    //horizontal detection
    if ((((swipe_det.eX - min_x > swipe_det.sX) || (swipe_det.eX + min_x < swipe_det.sX)) && ((swipe_det.eY < swipe_det.sY + max_y) && (swipe_det.sY > swipe_det.eY - max_y)))) {
      if(swipe_det.eX > swipe_det.sX) direc = "r";
      else direc = "l";
    }
    //vertical detection
    if ((((swipe_det.eY - min_y > swipe_det.sY) || (swipe_det.eY + min_y < swipe_det.sY)) && ((swipe_det.eX < swipe_det.sX + max_x) && (swipe_det.sX > swipe_det.eX - max_x)))) {
      if(swipe_det.eY > swipe_det.sY) direc = "d";
      else direc = "u";
    }

    if (direc != "") {
      if(typeof func == 'function') func(el,direc);
    }
    direc = "";
  },false);  
}

myfunction(el,d) {
  alert("you swiped on element with id '"+el+"' to "+d+" direction");
}

要使用该功能,只需像这样使用它

detectswipe('an_element_id',myfunction);

detectswipe('an_other_element_id',my_other_function);

如果检测到滑动,则使用参数 element-id 和“l,r,u,d”(左、右、上、下)调用函数“myfunction”。

示例:http://jsfiddle.net/rvuayqeo/1/

【讨论】:

    【解决方案3】:

    受@cocco 启发,我创建了一个更好的(非最小化)版本:

    (function(d) {
        // based on original source: https://stackoverflow.com/a/17567696/334451
        var newEvent = function(e, name) {
            // This style is already deprecated but very well supported in real world: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/initCustomEvent
            // in future we want to use CustomEvent function: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
            var a = document.createEvent("CustomEvent");
            a.initCustomEvent(name, true, true, e.target);
            e.target.dispatchEvent(a);
            a = null;
            return false
        };
        var debug = false; // emit info to JS console for all touch events?
        var active = false; // flag to tell if touchend should complete the gesture
        var min_gesture_length = 20; // minimum gesture length in pixels
        var tolerance = 0.3; // value 0 means pixel perfect movement up or down/left or right is required, 0.5 or more means any diagonal will do, values between can be tweaked
    
        var sp = { x: 0, y: 0, px: 0, py: 0 }; // start point
        var ep = { x: 0, y: 0, px: 0, py: 0 }; // end point
        var touch = {
            touchstart: function(e) {
                active = true;
                t = e.touches[0];
                sp = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
                ep = sp; // make sure we have a sensible end poin in case next event is touchend
                debug && console.log("start", sp);
            },
            touchmove: function(e) {
                if (e.touches.length > 1) {
                    active = false;
                    debug && console.log("aborting gesture because multiple touches detected");
                    return;
                }
                t = e.touches[0];
                ep = { x: t.screenX, y: t.screenY, px: t.pageX, py: t.pageY };
                debug && console.log("move", ep, sp);
            },
            touchend: function(e) {
                if (!active)
                    return;
                debug && console.log("end", ep, sp);
                var dx = Math.abs(ep.x - sp.x);
                var dy = Math.abs(ep.y - sp.y);
    
                if (Math.max(dx, dy) < min_gesture_length) {
                    debug && console.log("ignoring short gesture");
                    return; // too short gesture, ignore
                }
    
                if (dy > dx && dx/dy < tolerance && Math.abs(sp.py - ep.py) > min_gesture_length) { // up or down, ignore if page scrolled with touch
                    newEvent(e, (ep.y - sp.y < 0 ? 'gesture-up' : 'gesture-down'));
                    //e.cancelable && e.preventDefault();
                }
                else if (dx > dy && dy/dx < tolerance && Math.abs(sp.px - ep.px) > min_gesture_length) { // left or right, ignore if page scrolled with touch
                    newEvent(e, (ep.x - sp.x < 0 ? 'gesture-left' : 'gesture-right'));
                    //e.cancelable && e.preventDefault();
                }
                else {
                    debug && console.log("ignoring diagonal gesture or scrolled content");
                }
                active = false;
            },
            touchcancel: function(e) {
                debug && console.log("cancelling gesture");
                active = false;
            }
        };
        for (var a in touch) {
            d.addEventListener(a, touch[a], false);
            // TODO: MSIE touch support: https://github.com/CamHenlin/TouchPolyfill
        }
    })(window.document);
    

    与@cocco 原始版本相比的重要更改:

    • 使用event.touches[0].screenX/screenY 作为主要信息来源。 pageX/pageY 属性不能正确表示屏幕上的触摸移动,因为如果某些页面随着触摸滚动,它也会影响 pageX/pageY 值。
    • 添加最小手势长度设置
    • 添加忽略近对角手势的容差设置
    • 如果页面内容随手势滚动,则忽略该手势(在触发手势之前检查pageX/pageY 的差异)
    • 如果在手势期间进行了多次触摸,则中止手势

    未来需要做的事情:

    • 使用CustomEvent() function interface 代替createEvent() 方法。
    • 添加 MSIE 兼容性
    • 是否可以为pageX/pageY 配置最小手势长度,与screenX/screenY 分开?
    • 如果触摸移动太快,Chrome 的线程滚动似乎仍然会导致滚动检测出现一些问题。在决定是否应该触发事件之前,也许要等待下一帧,然后再决定滚动到哪里?

    用法如下:

    document.body.addEventListener('gesture-right', function (e) {  ... });
    

    或者jquery风格

    $("article").on("gesture-down", function (e) { ... });
    

    【讨论】:

    • 对结果的努力和清晰表示赞赏,干得好。
    【解决方案4】:

    所有这些代码都需要改进(就像您可以在触摸操作中找到的大多数代码一样)。

    玩触摸事件时,请记住用户有不止一根手指,触摸有一个标识符touches 列表代表所有当前触摸在表面上,甚至触摸没有移动的

    所以过程比较简单:

    1. ontouchstart:获取第一个更改的触摸(不是event.originalEvent.touches 属性,而是event.originalEvent.changedTouches 一个)。使用event.originalEvent.changedTouches[0].identifier 注册其标识符并查找要查找的触摸属性(pageX/pageYclientX/clientYDOMElement.getBoundingClientRect() 方法结合使用非常有用);

    2. ontouchmove:确保当前触摸在 changedTouches 列表中,event.originalEvent.changedTouches.identifiedTouch( identifier )。如果它什么都不返回,这意味着用户已经移动了另一个触摸(不是你正在寻找的那个)。还可以注册触摸属性以查找和执行您想要的任何操作。

    3. ontouchend:同样,您必须确保当前触摸在 changedTouches 列表中。使用触摸属性完成这项工作,最后丢弃您当前的触摸标识符。

    如果你想做得更强,可以考虑多次触摸(不仅仅是一次)来观察。

    有关 TouchEvent、TouchList 和 Touch on 的更多信息:https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events

    【讨论】:

      【解决方案5】:

      在触摸仍在移动时检测左右。

      这是通过保存最后一个位置并使用 timeout 在 touchmove 停止后擦除最后一个位置来完成的。

      var currentX;
      var lastX = 0;
      var lastT;
      $(document).bind('touchmove', function(e) {
          // If still moving clear last setTimeout
          clearTimeout(lastT);
      
          currentX = e.originalEvent.touches[0].clientX;
      
          // After stoping or first moving
          if(lastX == 0) {
              lastX = currentX;
          }
      
          if(currentX < lastX) {
              // Left
          } else if(currentX > lastX){
              // Right
          }
      
          // Save last position
          lastX = currentX;
      
          // Check if moving is done
          lastT = setTimeout(function() {
              lastX = 0;
          }, 100);
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-11
        • 1970-01-01
        • 1970-01-01
        • 2011-07-23
        • 1970-01-01
        • 2014-09-21
        • 1970-01-01
        相关资源
        最近更新 更多