ChatGPT解决这个技术问题 Extra ChatGPT

在 iPhone 和 Android 上通过 JavaScript 检测手指滑动

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

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

对于滑动识别,我会推荐 Hammer.js。它非常小,并且支持多种手势:- 滑动 - 旋转 - 捏合 - 按下(长按)- 点击 - 平移
有一个事件:“touchmove”
@Clay 那个仍然不能在 Safari 中工作,所以没有 iPhone。
@JohnDoherty 我同意这很棒!

N
Niket Pathak

简单的香草 JS 代码示例:

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);

var xDown = null;                                                        
var yDown = null;

function getTouches(evt) {
  return evt.touches ||             // browser API
         evt.originalEvent.touches; // jQuery
}                                                     
                                                                         
function handleTouchStart(evt) {
    const firstTouch = getTouches(evt)[0];                                      
    xDown = firstTouch.clientX;                                      
    yDown = firstTouch.clientY;                                      
};                                                
                                                                         
function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.touches[0].clientX;                                    
    var yUp = evt.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;
                                                                         
    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {
            /* right swipe */ 
        } else {
            /* left swipe */
        }                       
    } else {
        if ( yDiff > 0 ) {
            /* down swipe */ 
        } else { 
            /* up swipe */
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;                                             
};

在安卓中测试。


看起来很酷很简单,知道对这个事件 touchstarttouchmove 的支持是什么吗?
它工作得很好,但在检测直线运动时有问题。我将在这个主题上发布另一个答案,将其修复为 JQuery(桌面)解决方案。它还添加了这些滑动事件的鼠标版本并添加了灵敏度选项。
该死。主题已关闭,因此无法添加我的答案!
这很好用,但左/右和上/下是倒退的。
originalEvent 是一个 JQuery 属性。如果您在没有 JQuery 的情况下运行纯 javascript,则应该忽略它。如果在没有 JQuery 的情况下运行当前代码,则会引发异常。
D
Damian Pavlica

用于水平滑动的简单香草 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()
})

您可以使用完全相同的逻辑进行垂直滑动。


大声笑,这非常简单,甚至允许指定“旅行距离”。
迄今为止最好的答案..很遗憾它没有更多的赞成票..
@MattiaRasulo 可能需要上下滑动
这是一些非常好的代码,如果你想上下做所有你需要做的就是改变它的状态 X 并用 Y 替换。 Easyyyy
很好的答案,因为它很简单!值得注意的是,如果触摸点的 x 距离最小,则使用此代码还将垂直滑动检测为水平滑动。
J
John Doherty

我将这里的一些答案合并到一个脚本中,该脚本使用 CustomEvent 在 DOM 中触发滑动事件。将 0.7k swiped-events.min.js 脚本添加到您的页面并监听 swiped 事件:

刷过

document.addEventListener('swiped', function(e) {
    console.log(e.target); // the element that was swiped
    console.log(e.detail.dir); // swiped direction
});

向左滑动

document.addEventListener('swiped-left', function(e) {
    console.log(e.target); // the element that was swiped
});

向右滑动

document.addEventListener('swiped-right', function(e) {
    console.log(e.target); // the element that was swiped
});

向上滑动

document.addEventListener('swiped-up', function(e) {
    console.log(e.target); // the element that was swiped
});

向下滑动

document.addEventListener('swiped-down', function(e) {
    console.log(e.target); // the element that was swiped
});

您还可以直接附加到元素:

document.getElementById('myBox').addEventListener('swiped-down', function(e) {
    console.log(e.target); // the element that was swiped
});

可选配置

您可以指定以下属性来调整页面中滑动交互的功能(这些是可选的)。

<div data-swipe-threshold="10"
     data-swipe-timeout="1000"
     data-swipe-ignore="false">
      Swiper, get swiping!
</div>

要在应用程序范围内设置默认值,请在最顶层元素上设置配置属性:

<body data-swipe-threshold="100" data-swipe-timeout="250">
    <div>Swipe me</div>
    <div>or me</div>
</body>

源代码在 Github 上可用


我来到这里是因为纯滑动在 MOBILE 上不适合我
@StefanBob 如果您在 github repo 上打勾并提供足够的信息让我重现该问题,我会调查它
谢谢,它完美地工作!我用你的库替换了 Hammer.js,因为前者不适用于浏览器缩放,这是一个严重的可用性问题。使用这个库,缩放可以正常工作(在 Android 上测试)
Hammer.js 似乎不再维护
@JohnDoherty 你会扩展它,所以它也适用于桌面(非触摸)设备。
M
Marwelln

根据@givanse 的回答,您可以使用 classes 做到这一点:

class Swipe {
    constructor(element) {
        this.xDown = null;
        this.yDown = null;
        this.element = typeof(element) === 'string' ? document.querySelector(element) : element;

        this.element.addEventListener('touchstart', function(evt) {
            this.xDown = evt.touches[0].clientX;
            this.yDown = evt.touches[0].clientY;
        }.bind(this), false);

    }

    onLeft(callback) {
        this.onLeft = callback;

        return this;
    }

    onRight(callback) {
        this.onRight = callback;

        return this;
    }

    onUp(callback) {
        this.onUp = callback;

        return this;
    }

    onDown(callback) {
        this.onDown = callback;

        return this;
    }

    handleTouchMove(evt) {
        if ( ! this.xDown || ! this.yDown ) {
            return;
        }

        var xUp = evt.touches[0].clientX;
        var yUp = evt.touches[0].clientY;

        this.xDiff = this.xDown - xUp;
        this.yDiff = this.yDown - yUp;

        if ( Math.abs( this.xDiff ) > Math.abs( this.yDiff ) ) { // Most significant.
            if ( this.xDiff > 0 ) {
                this.onLeft();
            } else {
                this.onRight();
            }
        } else {
            if ( this.yDiff > 0 ) {
                this.onUp();
            } else {
                this.onDown();
            }
        }

        // Reset values.
        this.xDown = null;
        this.yDown = null;
    }

    run() {
        this.element.addEventListener('touchmove', function(evt) {
            this.handleTouchMove(evt).bind(this);
        }.bind(this), false);
    }
}

你可以这样使用它:

// Use class to get element by string.
var swiper = new Swipe('#my-element');
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// Get the element yourself.
var swiper = new Swipe(document.getElementById('#my-element'));
swiper.onLeft(function() { alert('You swiped left.') });
swiper.run();

// One-liner.
(new Swipe('#my-element')).onLeft(function() { alert('You swiped left.') }).run();

此代码可能不起作用,因为您在尝试调用未定义的 .bind 时会遇到异常,因为您的 handleTouchMove 实际上没有返回任何内容。使用 this. 调用函数时调用 bind 也是没用的,因为它已经绑定到当前上下文
我刚刚删除了 .bind(this);,它运行良好。谢谢@nicholas_r
部分自己获取元素我只需删除 document.getElementById('my-element') 中的 '#' 并且效果很好。谢谢@Marwelln :)
如果您想等到滑动结束(即在他们抬起手指或鼠标抬起之后),请将 touches[0] 更改为 changedTouches[0] 并将事件处理程序类型 handleTouchMove 更改为 handleTouchEnd
调用 run() 两次,您会遇到严重的内存泄漏
n
nashcheez

我发现@givanse 出色的答案是跨多个移动浏览器注册滑动操作的最可靠和兼容的答案。

但是,需要更改他的代码才能使其在使用 jQuery 的现代移动浏览器中运行。

如果使用 jQuery 并导致 undefined,则 event.touches 将不存在,应替换为 event.originalEvent.touches。如果没有 jQueryevent.touches 应该可以正常工作。

所以解决方案变成了,

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);

var xDown = null;                                                        
var yDown = null;                                                        

function handleTouchStart(evt) {                                         
    xDown = evt.originalEvent.touches[0].clientX;                                      
    yDown = evt.originalEvent.touches[0].clientY;                                      
};                                                

function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.originalEvent.touches[0].clientX;                                    
    var yUp = evt.originalEvent.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;

    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {
            /* left swipe */ 
        } else {
            /* right swipe */
        }                       
    } else {
        if ( yDiff > 0 ) {
            /* up swipe */ 
        } else { 
            /* down swipe */
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;                                             
};

测试:

安卓:Chrome、UC 浏览器

iOS:Safari、Chrome、UC 浏览器


originalEvent 是一个 JQuery 属性。它甚至不存在于纯 Javascript 中。
根据 this SO answer,如果浏览器支持的触摸事件将通过 event.originalEvent 公开。问题是 event.touches 现在已不复存在并导致 undefined
event.touches 仅在使用 JQuery 时不复存在。在没有 JQuery 的情况下尝试您的代码,您将收到 evt.originalEvent 未定义的错误。 JQuery 完全用自己的事件替换事件,并将本机浏览器事件放在 originalevent 中。简短版:您的代码仅适用于 JQuery。如果您删除 originalevent,它可以在没有 JQuery 的情况下工作。
是的,我进行了一些研究,并意识到您对启用 jquery event.originalEvent 的可用性是正确的。我会更新我的答案。谢谢! :)
更改 xDown = evt.originalEvent.touches[0].clientX; yDown = evt.originalEvent.touches[0].clientY;到 xDown = evt.offsetX; yDown = evt.offsetY;现在它就像普通 JS 的魅力一样。我喜欢这个解决方案。
a
alex

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


我相信可以使用的是 touchstart、touchmove、touchcancel 和 touchend,而不是 mousedown 或 mouseup。
N
Niklas Ekman

jQuery Mobile 还包括滑动支持:http://api.jquerymobile.com/swipe/

例子

$("#divId").on("swipe", function(event) {
    alert("It's a swipe!");
});

如果不想让 jQuery mobile 操作 UI,请参阅:stackoverflow.com/questions/8648596/…
r
rmnsh

一些最高级的答案(无法评论......)来处理短刷

document.addEventListener('touchstart', handleTouchStart, false);        
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;                                                        
var yDown = null;                                                        
function handleTouchStart(evt) {                                         
    xDown = evt.touches[0].clientX;                                      
    yDown = evt.touches[0].clientY;                                      
};                                                
function handleTouchMove(evt) {
    if ( ! xDown || ! yDown ) {
        return;
    }

    var xUp = evt.touches[0].clientX;                                    
    var yUp = evt.touches[0].clientY;

    var xDiff = xDown - xUp;
    var yDiff = yDown - yUp;
    if(Math.abs( xDiff )+Math.abs( yDiff )>150){ //to deal with to short swipes

    if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
        if ( xDiff > 0 ) {/* left swipe */ 
            alert('left!');
        } else {/* right swipe */
            alert('right!');
        }                       
    } else {
        if ( yDiff > 0 ) {/* up swipe */
            alert('Up!'); 
        } else { /* down swipe */
            alert('Down!');
        }                                                                 
    }
    /* reset values */
    xDown = null;
    yDown = null;
    }
};

M
Marc-André Lafortune

我已将 TouchWipe 重新打包为一个简短的 jquery 插件:detectSwipe


K
K-Gun

阈值,超时滑动,swipeBlockElems 添加。

document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
document.addEventListener('touchend', handleTouchEnd, false);     

const SWIPE_BLOCK_ELEMS = [
  'swipBlock',
  'handle',
  'drag-ruble'
]

let xDown = null;
let yDown = null; 
let xDiff = null;
let yDiff = null;
let timeDown = null;
const  TIME_THRESHOLD = 200;
const  DIFF_THRESHOLD = 130;

function handleTouchEnd() {

let timeDiff = Date.now() - timeDown; 
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
  if (Math.abs(xDiff) > DIFF_THRESHOLD && timeDiff < TIME_THRESHOLD) {
    if (xDiff > 0) {
      // console.log(xDiff, TIME_THRESHOLD, DIFF_THRESHOLD)
      SWIPE_LEFT(LEFT) /* left swipe */
    } else {
      // console.log(xDiff)
      SWIPE_RIGHT(RIGHT) /* right swipe */
    }
  } else {
    console.log('swipeX trashhold')
  }
} else {
  if (Math.abs(yDiff) > DIFF_THRESHOLD && timeDiff < TIME_THRESHOLD) {
    if (yDiff > 0) {
      /* up swipe */
    } else {
      /* down swipe */
    }
  } else {
    console.log('swipeY trashhold')
  }
 }
 /* reset values */
 xDown = null;
 yDown = null;
 timeDown = null; 
}
function containsClassName (evntarget , classArr) {
 for (var i = classArr.length - 1; i >= 0; i--) {
   if( evntarget.classList.contains(classArr[i]) ) {
      return true;
    }
  }
}
function handleTouchStart(evt) {
  let touchStartTarget = evt.target;
  if( containsClassName(touchStartTarget, SWIPE_BLOCK_ELEMS) ) {
    return;
  }
  timeDown = Date.now()
  xDown = evt.touches[0].clientX;
  yDown = evt.touches[0].clientY;
  xDiff = 0;
  yDiff = 0;

}

function handleTouchMove(evt) {
  if (!xDown || !yDown) {
    return;
  }

  var xUp = evt.touches[0].clientX;
  var yUp = evt.touches[0].clientY;


  xDiff = xDown - xUp;
  yDiff = yDown - yUp;
}

B
Basj

我只想检测左右滑动,但仅在触摸事件结束时触发动作,所以我稍微修改了@givanse's great answer来做到这一点。

为什么要这样做?例如,如果在滑动时,用户注意到他最终不想滑动,他可以在原始位置移动手指(一个非常流行的“约会”电话应用程序就是这样做的;)),然后“向右滑动”事件被取消。

所以为了避免“向右滑动”事件仅仅因为水平方向有 3px 的差异,我添加了一个阈值,在该阈值下一个事件被丢弃:为了有一个“向右滑动”事件,用户必须至少滑动浏览器宽度的 1/3(当然你可以修改这个)。

所有这些小细节都增强了用户体验。

请注意,当前,如果两根手指中的一个手指在双指缩放期间进行大的水平移动,则“触摸双指缩放”可能会被检测为滑动。

这是(Vanilla JS)代码:

var xDown = null, yDown = null, xUp = null, yUp = null;
document.addEventListener('touchstart', touchstart, false);        
document.addEventListener('touchmove', touchmove, false);
document.addEventListener('touchend', touchend, false);
function touchstart(evt) { const firstTouch = (evt.touches || evt.originalEvent.touches)[0]; xDown = firstTouch.clientX; yDown = firstTouch.clientY; }
function touchmove(evt) { if (!xDown || !yDown ) return; xUp = evt.touches[0].clientX; yUp = evt.touches[0].clientY; }
function touchend(evt) { 
    var xDiff = xUp - xDown, yDiff = yUp - yDown;
    if ((Math.abs(xDiff) > Math.abs(yDiff)) && (Math.abs(xDiff) > 0.33 * document.body.clientWidth)) { 
        if (xDiff < 0) 
            document.getElementById('leftnav').click();
        else
            document.getElementById('rightnav').click();
    } 
    xDown = null, yDown = null;
}

R
Rayjax

如果有人尝试在 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 上的滑动。

使用这两行它可以正常工作


我还需要补充:$.event.special.swipe.scrollSupressionThreshold = 8;,但你让我朝着正确的方向前进!谢谢!
T
Trendal Toews

当用户拖动手指时,我遇到了 touchend 处理程序连续触发的问题。我不知道这是否是由于我做错了什么,但我重新连接它以使用 touchmove 累积移动,而 touchend 实际上会触发回调。

我还需要拥有大量此类实例,因此我添加了启用/禁用方法。

以及一个不会触发短暂滑动的阈值。 Touchstart 每次都将计数器归零。

您可以即时更改 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);
            }
        }
    }
}

这是 ES5 还是 ES6 的?
@gigawatts 我不记得了。使用的项目已经达到 EOL,从那以后我就不需要代码了。我怀疑当时我正在为 ES6 写作,但那是 2 年前的事了。
1
1.21 gigawatts

添加到此答案 here。这个增加了对鼠标事件的支持,以便在桌面上进行测试:

<!--scripts-->
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.isMouseDown = false;
        this.listenForMouseEvents = true;
        this.options = Object.assign({ triggerPercent: 0.3 }, options);

        element.addEventListener('touchstart', evt => this.handleTouchStart(evt), false);
        element.addEventListener('touchend', evt => this.handleTouchEnd(evt), false);
        element.addEventListener('mousedown', evt => this.handleMouseDown(evt), false);
        element.addEventListener('mouseup', evt => this.handleMouseUp(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;
    }

    handleMouseDown(evt) {
        if (this.listenForMouseEvents==false) return;
        this.xDown = evt.clientX;
        this.yDown = evt.clientY;
        this.isMouseDown = true;
    }

    handleMouseUp(evt) {
        if (this.isMouseDown == false) return;
        const deltaX = evt.clientX - this.xDown;
        const deltaY = evt.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');
            }
        }
    }

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

// add a listener on load
window.addEventListener("load", function(event) {
    const dispatcher = new SwipeEventDispatcher(document.body);
    dispatcher.on('SWIPE_RIGHT', () => { console.log('I swiped right!') })
    dispatcher.on('SWIPE_LEFT', () => { console.log('I swiped left!') })
});

太棒了。
V
Vargr

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

在 gzip 压缩、缩小、babel 等之后,这大约是 450 字节。

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

像这样使用它:

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

导出类 SwipeEventDispatcher { constructor(element, options = {}) { this.evtMap = { SWIPE_LEFT: [], SWIPE_UP: [], SWIPE_DOWN: [], SWIPE_RIGHT: [] }; this.xDown = null; this.yDown = null; this.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);常量 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'); } 其他 { deltaY > 0 ? this.trigger('SWIPE_UP') : this.trigger('SWIPE_DOWN'); } } } } 导出默认 SwipeEventDispatcher;


R
Romanna Semenyshyn

我也合并了一些答案,主要是第一个和第二个与类,这是我的版本:

export default class Swipe {
    constructor(options) {
        this.xDown = null;
        this.yDown = null;

        this.options = options;

        this.handleTouchStart = this.handleTouchStart.bind(this);
        this.handleTouchMove = this.handleTouchMove.bind(this);

        document.addEventListener('touchstart', this.handleTouchStart, false);
        document.addEventListener('touchmove', this.handleTouchMove, false);

    }

    onLeft() {
        this.options.onLeft();
    }

    onRight() {
        this.options.onRight();
    }

    onUp() {
        this.options.onUp();
    }

    onDown() {
        this.options.onDown();
    }

    static getTouches(evt) {
        return evt.touches      // browser API

    }

    handleTouchStart(evt) {
        const firstTouch = Swipe.getTouches(evt)[0];
        this.xDown = firstTouch.clientX;
        this.yDown = firstTouch.clientY;
    }

    handleTouchMove(evt) {
        if ( ! this.xDown || ! this.yDown ) {
            return;
        }

        let xUp = evt.touches[0].clientX;
        let yUp = evt.touches[0].clientY;

        let xDiff = this.xDown - xUp;
        let yDiff = this.yDown - yUp;


        if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
            if ( xDiff > 0 && this.options.onLeft) {
                /* left swipe */
                this.onLeft();
            } else if (this.options.onRight) {
                /* right swipe */
                this.onRight();
            }
        } else {
            if ( yDiff > 0 && this.options.onUp) {
                /* up swipe */
                this.onUp();
            } else if (this.options.onDown){
                /* down swipe */
                this.onDown();
            }
        }

        /* reset values */
        this.xDown = null;
        this.yDown = null;
    }
}

之后可以如下使用它:

let swiper = new Swipe({
                    onLeft() {
                        console.log('You swiped left.');
                    }
});

当您只想调用“onLeft”方法时,它有助于避免控制台错误。


A
Abdur Rehman

用过两个:

jQuery mobile: 在大多数情况下都有效,特别是当您开发使用其他 jQuery 插件的应用程序时,最好使用 jQuery mobile 控件。在此处访问:https://www.w3schools.com/jquerymobile/jquerymobile_events_touch.asp

Hammer Time !最好的、轻量级和快速的基于 javascript 的库之一。在此处访问:https://hammerjs.github.io/


R
Ruben Martinez Jr.

我重新设计了 @givanse's solution 以用作 React 钩子。输入是一些可选的事件监听器,输出是一个功能性的引用(需要是功能性的,这样钩子可以在引用发生变化时重新运行)。

还添加了垂直/水平滑动阈值参数,以便小动作不会意外触发事件侦听器,但可以将这些设置为 0 以更接近地模仿原始答案。

提示:为获得最佳性能,应记住事件侦听器输入函数。

function useSwipeDetector({
    // Event listeners.
    onLeftSwipe,
    onRightSwipe,
    onUpSwipe,
    onDownSwipe,

    // Threshold to detect swipe.
    verticalSwipeThreshold = 50,
    horizontalSwipeThreshold = 30,
}) {
    const [domRef, setDomRef] = useState(null);
    const xDown = useRef(null);
    const yDown = useRef(null);

    useEffect(() => {
        if (!domRef) {
            return;
        }

        function handleTouchStart(evt) {
            const [firstTouch] = evt.touches;
            xDown.current = firstTouch.clientX;
            yDown.current = firstTouch.clientY;
        };

        function handleTouchMove(evt) {
            if (!xDown.current || !yDown.current) {
                return;
            }

            const [firstTouch] = evt.touches;
            const xUp = firstTouch.clientX;
            const yUp = firstTouch.clientY;
            const xDiff = xDown.current - xUp;
            const yDiff = yDown.current - yUp;

            if (Math.abs(xDiff) > Math.abs(yDiff)) {/*most significant*/
                if (xDiff > horizontalSwipeThreshold) {
                    if (onRightSwipe) onRightSwipe();
                } else if (xDiff < -horizontalSwipeThreshold) {
                    if (onLeftSwipe) onLeftSwipe();
                }
            } else {
                if (yDiff > verticalSwipeThreshold) {
                    if (onUpSwipe) onUpSwipe();
                } else if (yDiff < -verticalSwipeThreshold) {
                    if (onDownSwipe) onDownSwipe();
                }
            }
        };

        function handleTouchEnd() {
            xDown.current = null;
            yDown.current = null;
        }

        domRef.addEventListener("touchstart", handleTouchStart, false);
        domRef.addEventListener("touchmove", handleTouchMove, false);
        domRef.addEventListener("touchend", handleTouchEnd, false);

        return () => {
            domRef.removeEventListener("touchstart", handleTouchStart);
            domRef.removeEventListener("touchmove", handleTouchMove);
            domRef.removeEventListener("touchend", handleTouchEnd);
        };
    }, [domRef, onLeftSwipe, onRightSwipe, onUpSwipe, onDownSwipe, verticalSwipeThreshold, horizontalSwipeThreshold]);

    return (ref) => setDomRef(ref);
};

您有机会添加示例用法吗?
j
jgmjgm

您可能会更轻松地首先使用鼠标事件来实现它的原型。

这里有很多答案,包括顶部,应该谨慎使用,因为它们不考虑边缘情况,尤其是在边界框周围。

看:

触摸事件

鼠标事件

您将需要尝试捕捉边缘情况和行为,例如在结束之前将指针移出元素。

滑动是一个非常基本的手势,它是更高级别的界面指针交互处理,大致位于处理原始事件和手写识别之间。

没有一种精确的方法可以检测滑动或甩动,尽管几乎所有方法通常都遵循以距离和速度或速度阈值检测跨元素运动的基本原理。您可能会简单地说,如果在给定时间内在给定方向上有 65% 的屏幕尺寸发生移动,那么它就是滑动。确切地在哪里画线以及如何计算它取决于您。

有些人可能还会从一个方向的动量以及释放元素时它被推离屏幕多远的角度来看待它。使用粘性滑动可以更清晰地拖动元素,然后在释放时会弹回或飞离屏幕,就像松紧带坏了一样。

尝试找到一个手势库可能是理想的,您可以移植或重用通常用于一致性的手势库。这里的许多示例都过于简单化,将滑动记录为任何方向的最轻微触摸。

Android 将是显而易见的选择,尽管有相反的问题,它过于复杂。

许多人似乎把这个问题误解为一个方向的任何运动。滑动是一种广泛且相对简短的动作,绝大多数是在一个方向上(尽管可能是弧形并具有某些加速特性)。抛掷是类似的,尽管它打算在自己的动力下随意地将物品推开相当远的距离。

两者非常相似,以至于某些库可能只提供可以互换使用的 fling 或 swipe。在纯平屏幕上,很难真正区分这两种手势,一般来说,人们都在做这两种手势(滑动物理屏幕但扔掉屏幕上显示的 UI 元素)。

你最好的选择是不要自己做。已经有 a large number of JavaScript libraries for detecting simple gestures


И
Илья Зеленько

如何使用偏移量的示例。

// 至少 100 像素是一次滑动 // 您可以使用相对于屏幕大小的值:window.innerWidth * .1 const offset = 100; let xDown, yDown window.addEventListener('touchstart', e => { const firstTouch = getTouch(e); xDown = firstTouch.clientX; yDown = firstTouch.clientY; }); window.addEventListener('touchend', e => { if (!xDown || !yDown) { return; } const { clientX: xUp, clientY: yUp } = getTouch(e); const xDiff = xDown - xUp; const yDiff = yDown - yUp; const xDiffAbs = Math.abs(xDown - xUp); const yDiffAbs = Math.abs(yDown - yUp); // 至少 是滑动 if (Math.max(xDiffAbs, yDiffAbs) < offset ) { return; } if (xDiffAbs > yDiffAbs) { if ( xDiff > 0 ) { console.log('left'); } else { console.log('right'); } } else { if (yDiff > 0 ) { console.log('up'); } else { console.log('down'); } } });函数 getTouch (e) { return e.changedTouches[0] }


目前使用这个版本。如果重复滑动,我将如何防止它多次触发?我将它与动画功能一起用于横向滚动表单,当我多次滑动时,事情变得有点棘手,我的 div 开始在可见区域重叠。
d
dimButTries

我必须为轮播编写一个简单的脚本来检测向左或向右滑动。

我使用了指针事件而不是触摸事件。

我希望这对个人有用,我欢迎任何改进我的代码的见解;与非常优秀的 JS 开发人员一起加入这个线程,我感到相当尴尬。

function getSwipeX({elementId}) {

  this.e               = document.getElementsByClassName(elementId)[0];
  this.initialPosition = 0;
  this.lastPosition    = 0;
  this.threshold       = 200;
  this.diffInPosition  = null;
  this.diffVsThreshold = null;
  this.gestureState    = 0;

  this.getTouchStart = (event) => {
    event.preventDefault();
    if (window.PointerEvent) {
      this.e.setPointerCapture(event.pointerId);
    }
    return this.initalTouchPos = this.getGesturePoint(event);
  }

  this.getTouchMove  = (event) => {
    event.preventDefault();
    return this.lastPosition = this.getGesturePoint(event);
  }

  this.getTouchEnd   = (event) => {
    event.preventDefault();
    if (window.PointerEvent) {
      this.e.releasePointerCapture(event.pointerId);
    }
    this.doSomething();
    this.initialPosition = 0;
  }

  this.getGesturePoint = (event) => {
    this.point = event.pageX
    return this.point;
  }

  this.whatGestureDirection = (event) => {
    this.diffInPosition  = this.initalTouchPos - this.lastPosition;
    this.diffVsThreshold = Math.abs(this.diffInPosition) > this.threshold;
    (Math.sign(this.diffInPosition) > 0) ? this.gestureState = 'L' : (Math.sign(this.diffInPosition) < 0) ? this.gestureState = 'R' : this.gestureState = 'N';
    
    return [this.diffInPosition, this.diffVsThreshold, this.gestureState];
  }

  this.doSomething = (event) => {
    let [gestureDelta,gestureThreshold,gestureDirection] = this.whatGestureDirection();

    // USE THIS TO DEBUG
    console.log(gestureDelta,gestureThreshold,gestureDirection);

    if (gestureThreshold) {
      (gestureDirection == 'L') ? // LEFT ACTION : // RIGHT ACTION
    }
  }

  if (window.PointerEvent) {
    this.e.addEventListener('pointerdown', this.getTouchStart, true);
    this.e.addEventListener('pointermove', this.getTouchMove, true);
    this.e.addEventListener('pointerup', this.getTouchEnd, true);
    this.e.addEventListener('pointercancel', this.getTouchEnd, true);
  }
}

您可以使用 new 调用该函数。

window.addEventListener('load', () => {
  let test = new getSwipeX({
    elementId: 'your_div_here'
  });
})

S
Segebee

我重新设计了 @ruben-martinez answer 以使用来自 @givanse 的惊人解决方案来使用自定义反应挂钩处理滑动事件。

import React, { useEffect, useRef, useState } from "react";

export default function useSwiper() {
  const [domRef, setDomRef] = useState<any>();

  const xDown: React.MutableRefObject<number | null> = useRef(null);
  const yDown: React.MutableRefObject<number | null> = useRef(null);

  useEffect(() => {
if (!domRef) return;

function getTouches(event: React.TouchEvent<HTMLDivElement>) {
  return event.touches;
}

function handleTouchStart(event: any) {
  const firstTouch = getTouches(event)[0];
  xDown.current = firstTouch.clientX;
  yDown.current = firstTouch.clientY;
}

function handleTouchMove(event: React.TouchEvent<HTMLDivElement>) {
  if (!xDown.current || !yDown.current) return;

  const firstTouch = getTouches(event)[0];
  const xUp = firstTouch.clientX;
  const yUp = firstTouch.clientY;

  const xDiff = xDown.current - xUp;
  const yDiff = yDown.current - yUp;

  if (Math.abs(xDiff) > Math.abs(yDiff)) {
    // handle horizontal swipes
    if (xDiff > 0) {
      // we swiped right
      console.log("right");
    } else {
      // we swiped left
      console.log("left");
    }
  } else {
    // handle vertical swipes
    if (yDiff > 0) {
      // we swiped down
      console.log("down");
    } else {
      // we swiped up
      console.log("up");
    }
  }
}

function handleTouchEnd(event: React.TouchEvent<HTMLDivElement>) {
  xDown.current = null;
  yDown.current = null;
}


  domRef.addEventListener("touchstart", handleTouchStart, false);
  domRef.addEventListener("touchmove", handleTouchMove, false);
  domRef.addEventListener("touchend", handleTouchEnd, false);

return () => {
    domRef.removeEventListener("touchstart", handleTouchStart, false);
    domRef.removeEventListener("touchmove", handleTouchMove, false);
    domRef.removeEventListener("touchend", handleTouchEnd, false);
};
  }, [domRef]);

  return (ref: any) => setDomRef(ref);
}

我实现他的答案的主要挑战是不知道如何将滑动元素的引用绑定到自定义钩子的引用。

基本上,发生的事情是我们从自定义钩子返回一个函数。这个函数将允许我们从我们想要监听滑动动作的元素中传入一个 ref。收到 ref 的自定义钩子然后用元素的 ref 更新钩子状态,触发重新渲染,这样我们就有了实际的元素!

这种函数式 ref 样式还允许我们将钩子用于多个元素。如下所示,我想将它用于项目列表以启用滑动删除:)

import useSwiper from "./hooks/useSwipe";

const EntryCard = ({ entry, godMode, reload }: EntryProps) => {
const swiperRef = useSwiper();

const handleEntryClick =
(entry: Entry) => async (event: React.MouseEvent<HTMLDivElement>) => {
  if (!godMode) return;

  try {
    reload((state) => !state);
  } catch (err) {
    console.log("Error deleting entry: ", err);
  }
};

return (
  <div className="item" onClick={handleEntryClick(entry)} ref={swiperRef}>
    <div className="username">{entry.userName}</div>
    <div className="score">{entry.weekScore}</div>
  </div>
 );
};

PS:您可以将函数传递给您的钩子以接收滑动值。谢谢你:)如果你喜欢投票:)


j
javad hemati

通过 touchStart 和 touchEnd 处理:

var handleSwipe = function(elem,callbackOnRight, callbackOnLeft, callbackOnDown, 
      callbackOnUp) => {

        elem.ontouchstart = handleTouchStart;
        elem.ontouchend = handleTouchEnd;

        var xDown = null;
        var yDown = null;

        function getTouches(evt) {
            return evt.touches ||             // browser API
                evt.originalEvent.touches; // jQuery
        }

        function handleTouchStart(evt) {
            const firstTouch = getTouches(evt)[0];
            xDown = firstTouch.clientX;
            yDown = firstTouch.clientY;
        };

        function handleTouchEnd(evt) {
            if (!xDown || !yDown) {
                return;
            }

            var xUp = evt.changedTouches[0].clientX;
            var yUp = evt.changedTouches[0].clientY;

            var xDiff = xDown - xUp;
            var yDiff = yDown - yUp;
            var minDif = 30;

            console.log(`xDiff:${xDiff}, yDiff:${yDiff}`);

            if (Math.abs(xDiff) > Math.abs(yDiff)) {
                if (xDiff > minDif) {
                    if (callbackOnLeft)
                        callbackOnLeft();
                } else if (xDiff < -1 * minDif){
                    if (callbackOnRight)
                        callbackOnRight();
                }
            } else {
                if (yDiff > minDif) {
                    if (callbackOnDown)
                        callbackOnDown();
                } else if (yDiff < -1* minDif){
                    if (callbackOnUp)
                        callbackOnUp();
                }
            }
            
            xDown = null;
            yDown = null;
        };
    }

z
zurfyx

您可以监听 touchstarttouchend 事件并根据事件数据 (Codepen) 计算方向和力:

让开始=空; document.addEventListener('touchstart', e => { const touch = e.changedTouches[0]; start = [touch.clientX, touch.clientY]; }); document.addEventListener('touchend', e => { const touch = e.changedTouches[0]; const end = [touch.clientX, touch.clientY]; document.body.innerText = `${end[0] - 开始[0]},${end[1] - 开始[1]}`; });在这里滑动

或者,您可以围绕同样的概念构建更符合人体工程学的 API (Codepen):

const removeListener = addSwipeRightListener(document, (force, e) => {
  console.info('Swiped right with force: ' + force);
});
// removeListener()

// swipe.js const { addSwipeLeftListener, addSwipeRightListener, addSwipeUpListener, addSwipeDownListener, } = (function() { // const elements = new WeakMap(); function readTouch(e) { const touch = e.changedTouches[0]; if (touch == undefined) { return null; } return [touch.clientX, touch.clientY]; } function addListener(element, cb) { let elementValues = elements.get(element); if (elementValues === undefined) { const listeners = new Set(); const handleTouchstart = e => { elementValues.start = readTouch(e); }; const handleTouchend = e => { const start = elementValues.start; if (start === null) { return; } const end = readTouch(e); for (const listener of listeners) { listener([end[0] - start[0], end[1 ] - start[1]], e); } }; element.addEventListener('touchstart', handleTouchstart); element.addEventListener('touchend', handleTouchend); elementValues = { start: null, listeners, handleTouchstart, handleTouchend, } ; 元素.set(元素,元素值); } elementValues.listeners.add(cb); return () => deleteListener(element, cb); } function deleteListener(element, cb) { const elementValues = elements.get(element); const listeners = elementValues.listeners; listeners.delete(cb); if (listeners.size === 0) { elements.delete(element); element.removeEventListener('touchstart', elementValues.handleTouchstart); element.removeEventListener('touchend', elementValues.handleTouchend); } } function addSwipeLeftListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (x < 0 && -x > Math.abs(y)) { cb (x, e); } }); } function addSwipeRightListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (x > 0 && x > Math.abs(y)) { cb(x , e); } }); } function addSwipeUpListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (y < 0 && -y > Math.abs(x)) { cb( x, e); } }); } function addSwipeDownListener(element, cb) { return addListener(element, (force, e) => { const [x, y] = force; if (y > 0 && y > Math.abs(x)) { cb(x , e); } }); } 返回 { addSwipeLeftListener, addSwipeRightListener, addSwipeUpListener, addSwipeDownListener, } })(); // app.js 函数 print(direction, force) { document.querySelector('#direction').innerText = direction; document.querySelector('#data').innerText = force; } addSwipeLeftListener(document, (force, e) => { print('left', force); }); addSwipeRightListener(document, (force, e) => { print('right', force); }); addSwipeUpListener(document, (force, e) => { print('up', force); }); addSwipeDownListener(document, (force, e) => { print('down', force); });

滑动

力度 (px):