jQuery 检测触摸设备上的左/右滑动,但允许向上/向下滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17567344/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 19:19:51  来源:igfitidea点击:

Detect left/right-swipe on touch-devices, but allow up/down-scrolling

javascriptjquerytouchswipe

提问by Raphael Jeger

I need to detect and react to left/right-swipes, but want to give the user the ability to scroll on the same element, so as long as he moves his finger only left/right with a maximum up/down movement of X pixels, it should not scroll, but when he exceeds X, it should scroll.

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

So what I did is:

所以我所做的是:

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();
    }

But I doesn't restore the ability to scroll in the "if" case...

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

Thanks for any tips.

感谢您提供任何提示。

回答by cocco

I wrote my own touch handler events.maybe this helps you

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

it checks for:

它检查:

fast click : 'fc'

快速点击:'fc'

swipe left : 'swl'

向左滑动:'swl'

swipe right : 'swr'

向右滑动:'swr'

swipe up : 'swu'

向上滑动:'swu'

swipe down : 'swd'

向下滑动:'swd'

each check initializes it's correspondent event.but you can scroll and do whatever else you do normally. you just have some new events.

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

you need swl swr, I aslo suggest to use fc (fastclick) for click events... it's much faster than normal click.

你需要 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);
}

in this case h is my handler for every type of event and i add the handlers to the body.

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

for what i understand your question you just have to write

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

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

to handle multiple elements and the same function... just add one handler.

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

so if you have

所以如果你有

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

you do:

你做:

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

same for fc & swr

fc 和 swr 相同

there is a bug in ios: don't use alert() .. it will execute 2 times.

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

回答by EscapeNetscape

There is a "bug" in the accepted answer. If you don't use Chrome on Android but the build in browser or a "webview" (For a html5-hybrid-app) for example, then the swipe is not being detected.

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

I found out that the event doesn't fire, because of the normal scroll behavior. So adding "e.preventDefault();" in touchmove would fix it or the fix from Eric Fuller in the accepted answer.

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

It's a nice snipped but in a mobile WebApp or Website this could result in a bad scroll stuttering, because the touch-events are observed the whole time.

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

So I decided to build something new. It's not as comfortable like to have new event listeners, but it's comfortable enough for my needs and it's performat.

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

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");
}

To use the function just use it like

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

detectswipe('an_element_id',myfunction);

detectswipe('an_other_element_id',my_other_function);

If a swipe is detected the function "myfunction" is called with parameter element-id and "l,r,u,d" (left,right,up,down).

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

Example: http://jsfiddle.net/rvuayqeo/1/

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

回答by Loops

All of these codes need improvement (like most of the codes that you can find on touch manipulation).

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

When playing with touch event, keep in mind that user have more than one finger, that a touch has an identifierand that toucheslist represent all current touches on the surface, even touches that have not moved.

在玩触摸事件时,请记住用户有多个手指,触摸有一个标识符,该touches列表表示表面上的所有当前触摸,甚至是尚未移动的触摸。

So the process is relatively simple:

所以过程比较简单:

  1. ontouchstart: get the first changed touch (not event.originalEvent.touchesproperty, but event.originalEvent.changedTouchesone). Register its identifier with event.originalEvent.changedTouches[0].identifierand touch properties to look for (pageX/pageYor clientX/clientYthat are pretty usefull in combination with DOMElement.getBoundingClientRect()method);

  2. ontouchmove: make sure that the current touch is in the changedTouches list with event.originalEvent.changedTouches.identifiedTouch( identifier ). If it return nothing, that means that the user has moved another touch (not the one you are looking for). Also register touch properties to look for and do whatever you want with.

  3. ontouchend: again, you must be sure the current touch is in changedTouches list. Do the job with touch properties and finally discard your current touch identifier.

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

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

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

If you want to do it stronger, consider multiple touches (not only one) to observe.

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

More information about TouchEvent, TouchList and Touch on: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Touch_events

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

回答by Mikko Rantalainen

Inspired by @cocco I created a better (non-minimized) version:

受@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);

Important changes compared to original version by @cocco:

与@cocco 的原始版本相比的重要变化:

  • use event.touches[0].screenX/screenYas the major source of information. The pageX/pageYproperties do not correctly represent the movement of touches on screen because if some piece of page scrolls with the touch, it affects the pageX/pageYvalues, too.
  • add minimum gesture length setting
  • add tolerance setting for ignoring near diagonal gestures
  • ignore the gesture if page content has scrolled with the gesture (inspect difference in pageX/pageYbefore triggering gesture)
  • abort gesture if multiple touches are done during the gesture
  • 利用event.touches[0].screenX/screenY作为信息的主要来源。这些pageX/pageY属性不能正确表示屏幕上触摸的移动,因为如果某些页面随着触摸滚动,它pageX/pageY也会影响值。
  • 添加最小手势长度设置
  • 添加忽略近对角手势的容差设置
  • 如果页面内容随手势滚动,则忽略手势(pageX/pageY在触发手势之前检查差异)
  • 如果在手势期间进行了多次触摸,则中止手势

Things that would need to be done in the future:

未来需要做的事情:

  • use CustomEvent()function interfaceinstead of createEvent()method.
  • add MSIE compatibility
  • maybe configure minimum gesture length for pageX/pageYseparate from screenX/screenY?
  • It seems that Chrome's threaded scrolling still causes some problems with scrolling detection if touch movement is too fast. Perhaps wait for next frame before deciding where scrolling has gone before deciding if event should be triggered?
  • 使用CustomEvent()函数接口代替createEvent()方法。
  • 添加 MSIE 兼容性
  • 也许配置最小手势长度以pageX/pageY分开screenX/screenY
  • 如果触摸移动太快,Chrome 的线程滚动似乎仍然会导致滚动检测出现一些问题。也许在决定是否应该触发事件之前,在决定滚动到哪里之前等待下一帧?

Usage is as follows:

用法如下:

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

or jquery style

或 jquery 风格

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

回答by Nebojsa Sapic

Detecting left and right while touch is still moving.

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

This is done with saving last position and using timeoutfor erasing last position after touchmove stop.

这是通过保存最后一个位置并在 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);
});