【问题标题】:Detect a finger swipe through JavaScript on the iPhone and Android在 iPhone 和 Android 上通过 JavaScript 检测手指滑动
【发布时间】:2011-01-16 20:52:55
【问题描述】:

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

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

【问题讨论】:

  • 对于滑动识别,我推荐Hammer.js。它很小,并且支持多种手势:- 滑动 - 旋转 - 捏合 - 按压(长按)- 点击 - 平移
  • 有一个事件:“touchmove”
  • @Clay 在 Safari 中仍然无法使用,所以没有 iPhone。
  • 2020 年swiped-events 是要走的路
  • @JohnDoherty 我同意这很棒!

标签: javascript iphone android swipe


【解决方案1】:

通过 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;
        };
    }

【讨论】:

    【解决方案2】:

    水平滑动的简单香草 JS 示例:

    let touchstartX = 0
    let touchendX = 0
    
    const slider = document.getElementById('slider')
    
    function handleGesture() {
      if (touchendX < touchstartX) alert('swiped left!')
      if (touchendX > touchstartX) alert('swiped right!')
    }
    
    slider.addEventListener('touchstart', e => {
      touchstartX = e.changedTouches[0].screenX
    })
    
    slider.addEventListener('touchend', e => {
      touchendX = e.changedTouches[0].screenX
      handleGesture()
    })
    

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

    【讨论】:

    • 哈哈,这太简单了,甚至允许指定“旅行距离”。
    • 迄今为止最好的答案.. 可惜没有更多的赞成票..
    • @MattiaRasulo 可能需要添加上下滑动
    【解决方案3】:

    我对 @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);
    }
    

    我实现他的答案的主要挑战是不知道如何将 swipe 元素的 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:您可以将函数传递给您的钩子以接收滑动值。谢谢你:)如果你喜欢投票:)

    【讨论】:

      【解决方案4】:

      简单的原生 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;                                             
      };
      

      在 Android 中测试。

      【讨论】:

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

      我只想检测左右滑动,但仅在触摸事件结束时触发动作,所以我稍微修改了@givanse 的最佳答案来做到这一点.

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

      所以为了避免“向右滑动”事件仅仅因为水平方向有 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;
      }
      

      【讨论】:

        【解决方案6】:

        我将这里的一些答案合并到一个脚本中,该脚本使用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 似乎不再维护了
        【解决方案7】:

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

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

        我希望这对个人有用,我欢迎任何见解来改进我的代码;与非常优秀的 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'
          });
        })
        

        【讨论】:

          【解决方案8】:

          阈值、超时滑动、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;
          }
          

          【讨论】:

            【解决方案9】:

            我重新设计了 @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);
            };
            

            【讨论】:

            • 您可以添加示例用法吗?
            【解决方案10】:

            添加到这个答案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!') })
            });
            

            【讨论】:

            • 这是完美的。
            【解决方案11】:

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

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

            见:

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

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

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

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

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

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

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

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

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

            【讨论】:

              【解决方案12】:

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

              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”方法时,有助于避免控制台错误。

              【讨论】:

                【解决方案13】:

                一个如何使用offset的例子。

                // at least 100 px are a swipe
                // you can use the value relative to screen size: 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);
                
                  // at least <offset> are a swipe
                  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');
                    }
                  }
                });
                
                function getTouch (e) {
                  return e.changedTouches[0]
                }

                【讨论】:

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

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

                这是经过 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;

                【讨论】:

                  【解决方案15】:

                  用过两个:

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

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

                  【讨论】:

                    【解决方案16】:

                    当用户四处拖动手指时,我遇到了 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 年前的事了。
                    【解决方案17】:

                    我发现@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;                                             
                    };
                    

                    测试于:

                    • Android: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 的情况下工作。
                    • 是的,我进行了一些研究,并意识到您对启用event.originalEvent 的 jquery 的可用性是正确的。我会更新我的答案。谢谢! :)
                    • 已更改 xDown = evt.originalEvent.touches[0].clientX; yDown = evt.originalEvent.touches[0].clientY;到 xDown = evt.offsetX; yDown = evt.offsetY;现在它就像普通 JS 的魅力一样。我喜欢这个解决方案。
                    【解决方案18】:

                    根据@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() 两次,你会遇到严重的内存泄漏
                    【解决方案19】:

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

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

                    【讨论】:

                      【解决方案20】:

                      如果有人尝试在 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; 但你让我朝着正确的方向前进!谢谢!
                      【解决方案21】:

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

                      【讨论】:

                        【解决方案22】:

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

                        例子

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

                        【讨论】:

                        【解决方案23】:

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

                        【讨论】:

                        • 我相信可以使用的是 touchstart、touchmove、touchcancel 和 touchend,而不是 mousedown 或 mouseup。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-08-27
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-05-27
                        相关资源
                        最近更新 更多