如何检测用户用JavaScript在网页上向某个方向滑动手指?

我想知道是否有一种解决方案可以同时适用于iPhone和Android手机上的网站。


当前回答

我遇到了触摸端处理程序连续发射的问题,而用户正在拖着一个手指。我不知道这是不是因为我做错了什么但是我重新连接了这个用touchmove来累积移动touchend实际上触发了回调。

我还需要大量这样的实例,所以我添加了启用/禁用方法。

还有一个阈值,短刷不会触发。每次Touchstart 0的计数器。

您可以动态地更改target_node。创建时启用是可选选项。

/** Usage: */
touchevent = new Modules.TouchEventClass(callback, target_node);
touchevent.enable();
touchevent.disable();

/** 
*
*   Touch event module
*
*   @param method   set_target_mode
*   @param method   __touchstart
*   @param method   __touchmove
*   @param method   __touchend
*   @param method   enable
*   @param method   disable
*   @param function callback
*   @param node     target_node
*/
Modules.TouchEventClass = class {

    constructor(callback, target_node, enable=false) {

        /** callback function */
        this.callback = callback;

        this.xdown = null;
        this.ydown = null;
        this.enabled = false;
        this.target_node = null;

        /** move point counts [left, right, up, down] */
        this.counts = [];

        this.set_target_node(target_node);

        /** Enable on creation */
        if (enable === true) {
            this.enable();
        }

    }

    /** 
    *   Set or reset target node
    *
    *   @param string/node target_node
    *   @param string      enable (optional)
    */
    set_target_node(target_node, enable=false) {

        /** check if we're resetting target_node */
        if (this.target_node !== null) {

            /** remove old listener */
           this.disable();
        }

        /** Support string id of node */
        if (target_node.nodeName === undefined) {
            target_node = document.getElementById(target_node);
        }

        this.target_node = target_node;

        if (enable === true) {
            this.enable();
        }
    }

    /** enable listener */
    enable() {
        this.enabled = true;
        this.target_node.addEventListener("touchstart", this.__touchstart.bind(this));
        this.target_node.addEventListener("touchmove", this.__touchmove.bind(this));
        this.target_node.addEventListener("touchend", this.__touchend.bind(this));
    }

    /** disable listener */
    disable() {
        this.enabled = false;
        this.target_node.removeEventListener("touchstart", this.__touchstart);
        this.target_node.removeEventListener("touchmove", this.__touchmove);
        this.target_node.removeEventListener("touchend", this.__touchend);
    }

    /** Touchstart */
    __touchstart(event) {
        event.stopPropagation();
        this.xdown = event.touches[0].clientX;
        this.ydown = event.touches[0].clientY;

        /** reset count of moves in each direction, [left, right, up, down] */
        this.counts = [0, 0, 0, 0];
    }

    /** Touchend */
    __touchend(event) {
        let max_moves = Math.max(...this.counts);
        if (max_moves > 500) { // set this threshold appropriately
            /** swipe happened */
            let index = this.counts.indexOf(max_moves);
            if (index == 0) {
                this.callback("left");
            } else if (index == 1) {
                this.callback("right");
            } else if (index == 2) {
                this.callback("up");
            } else {
                this.callback("down");
            }
        }
    }

    /** Touchmove */
    __touchmove(event) {

        event.stopPropagation();
        if (! this.xdown || ! this.ydown) {
            return;
        }

        let xup = event.touches[0].clientX;
        let yup = event.touches[0].clientY;

        let xdiff = this.xdown - xup;
        let ydiff = this.ydown - yup;

        /** Check x or y has greater distance */
        if (Math.abs(xdiff) > Math.abs(ydiff)) {
            if (xdiff > 0) {
                this.counts[0] += Math.abs(xdiff);
            } else {
                this.counts[1] += Math.abs(xdiff);
            }
        } else {
            if (ydiff > 0) {
                this.counts[2] += Math.abs(ydiff);
            } else {
                this.counts[3] += Math.abs(ydiff);
            }
        }
    }
}

其他回答

简单的水平滑动JS示例:

let touchstartX = 0
let touchendX = 0
    
function checkDirection() {
  if (touchendX < touchstartX) alert('swiped left!')
  if (touchendX > touchstartX) alert('swiped right!')
}

document.addEventListener('touchstart', e => {
  touchstartX = e.changedTouches[0].screenX
})

document.addEventListener('touchend', e => {
  touchendX = e.changedTouches[0].screenX
  checkDirection()
})

垂直滑动也可以使用相同的逻辑。

函数检查水平方向和垂直方向,以确定哪个滑动更长,以防止执行两个指令,因为不可能进行完美的单向滑动。滑动总是在X和Y上有偏差。

let touchstartX = 0;
let touchendX = 0;
let touchstartY = 0;
let touchendY = 0;   

function checkDirection() {
    let difX = touchstartX - touchendX;
    let difY = touchstartY - touchendY;
if (Math.abs(difX) > Math.abs(difY)) {
    if (touchendX < touchstartX) {/*left*/}
    if (touchendX > touchstartX) {/*right*/}
} else {
    if (touchendY < touchstartY) {/*up*/}
    if (touchendY > touchstartY) {/*down*/}
}
};
document.addEventListener('touchstart', e => {
    e.preventDefault();
    touchstartX = e.changedTouches[0].screenX;
    touchstartY = e.changedTouches[0].screenY;
});

document.addEventListener('touchend', e => {
    e.preventDefault();
    touchendX = e.changedTouches[0].screenX;
    touchendY = e.changedTouches[0].screenY;
    checkDirection();
});

如果你只需要滑动,你最好只使用你需要的部分。 这应该适用于任何触摸设备。

这是经过gzip压缩,缩小,babel等大约450字节。

我根据其他答案编写了下面的类,它使用移动百分比而不是像素,以及一个事件分派器模式来挂钩/取消挂钩。

像这样使用它:

const dispatcher = new SwipeEventDispatcher(myElement);
dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })

export class SwipeEventDispatcher { constructor(element, options = {}) { this.evtMap = { SWIPE_LEFT: [], SWIPE_UP: [], SWIPE_DOWN: [], SWIPE_RIGHT: [] }; this.xDown = null; this.yDown = null; this.element = element; this.options = Object.assign({ triggerPercent: 0.3 }, options); element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false); element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false); } on(evt, cb) { this.evtMap[evt].push(cb); } off(evt, lcb) { this.evtMap[evt] = this.evtMap[evt].filter(cb => cb !== lcb); } trigger(evt, data) { this.evtMap[evt].map(handler => handler(data)); } handleTouchStart(evt) { this.xDown = evt.touches[0].clientX; this.yDown = evt.touches[0].clientY; } handleTouchEnd(evt) { const deltaX = evt.changedTouches[0].clientX - this.xDown; const deltaY = evt.changedTouches[0].clientY - this.yDown; const distMoved = Math.abs(Math.abs(deltaX) > Math.abs(deltaY) ? deltaX : deltaY); const activePct = distMoved / this.element.offsetWidth; if (activePct > this.options.triggerPercent) { if (Math.abs(deltaX) > Math.abs(deltaY)) { deltaX < 0 ? this.trigger('SWIPE_LEFT') : this.trigger('SWIPE_RIGHT'); } else { deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN'); } } } } export default SwipeEventDispatcher;

如果有人试图在Android上使用jQuery Mobile,并且有JQM滑动检测的问题

(我在Xperia Z1, Galaxy S3, Nexus 4和一些Wiko手机上也有一些)这可能很有用:

 //Fix swipe gesture on android
    if(android){ //Your own device detection here
        $.event.special.swipe.verticalDistanceThreshold = 500
        $.event.special.swipe.horizontalDistanceThreshold = 10
    }

在android上的滑动不会被检测到,除非它是一个非常长、精确和快速的滑动。

这两条线可以正常工作

我之前使用的方法是,您必须检测mousedown事件,记录其x,y位置(任何相关的位置),然后检测mouseup事件,并减去两个值。