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

前端 未结 5 1460
逝去的感伤
逝去的感伤 2020-12-04 11:24

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

5条回答
  •  醉梦人生
    2020-12-04 11:58

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

    (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:

    • use event.touches[0].screenX/screenY as the major source of information. The pageX/pageY properties do not correctly represent the movement of touches on screen because if some piece of page scrolls with the touch, it affects the pageX/pageY values, 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/pageY before triggering gesture)
    • abort gesture if multiple touches are done during the gesture

    Things that would need to be done in the future:

    • use CustomEvent() function interface instead of createEvent() method.
    • add MSIE compatibility
    • maybe configure minimum gesture length for pageX/pageY separate 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?

    Usage is as follows:

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

    or jquery style

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

提交回复
热议问题