指尖上的事件-touch事件的封裝

在觸屏設備上,一些比較基礎的手勢都需要通過對 touch 事件進行二次封裝才能實現。

zepto 是移動端上使用率比較高的一個類庫,但是其 touch 模塊模擬出來的一些事件存在一些兼容性問題,如 tap 事件在某些安卓設備上存在事件穿透的 bug,其他類型的事件也或多或少的存在一些兼容性問題。

于是乎,干脆自己動手對這些常用的手勢事件進行了封裝,由于沒有太多真實的設備來進行測試,可能存在一些兼容性問題,下面的代碼也只是在 iOS 7、Andorid 4 上的一些比較常見的瀏覽器中測試通過。

tap事件

tap 事件相當于 pc 瀏覽器中的 click 效果,雖然在觸屏設備上 click 事件仍然可用,但是在很多設備上,click 會存在一些延遲,如果想要快速響應的 "click" 事件,需要借助 touch 事件來實現。


    var startTx, startTy;

    element.addEventListener( 'touchstart', function( e ){
      var touches = e.touches[0];

      startTx = touches.clientX;
      startTy = touches.clientY;
    }, false );

    element.addEventListener( 'touchend', function( e ){
      var touches = e.changedTouches[0],
        endTx = touches.clientX,
        endTy = touches.clientY;

      // 在部分設備上 touch 事件比較靈敏,導致按下和松開手指時的事件坐標會出現一點點變化
      if( Math.abs(startTx - endTx) < 6 && Math.abs(startTy - endTy) < 6 ){
        console.log( 'fire tap event' );
      }
    }, false );

doubleTap事件

doubleTap 事件是當手指在相同位置范圍內和極短的時間內兩次敲擊屏幕時觸發的事件。在部分瀏覽器下,doubleTap 事件會選中文本,如果不希望選中文本,可以給元素添加 user-select:none 的 css 屬性。


    var isTouchEnd = false,
      lastTime = 0,
      lastTx = null,
      lastTy = null,
      firstTouchEnd = true,
      body = document.body,
      dTapTimer, startTx, startTy, startTime;

    element.addEventListener( 'touchstart', function( e ){
      if( dTapTimer ){
        clearTimeout( dTapTimer );
        dTapTimer = null;
      }

      var touches = e.touches[0];

      startTx = touches.clientX;
      startTy = touches.clientY;   
    }, false );

    element.addEventListener( 'touchend', function( e ){
      var touches = e.changedTouches[0],
        endTx = touches.clientX,
        endTy = touches.clientY,
        now = Date.now(),
        duration = now - lastTime;

      // 首先要確保能觸發單次的 tap 事件
      if( Math.abs(startTx - endTx) < 6 && Math.abs(startTx - endTx) < 6 ){
        // 兩次 tap 的間隔確保在 500 毫秒以內
        if( duration < 301 ){
          // 本次的 tap 位置和上一次的 tap 的位置允許一定范圍內的誤差
          if( lastTx !== null &&
            Math.abs(lastTx - endTx) < 45 &&
            Math.abs(lastTy - endTy) < 45 ){

            firstTouchEnd = true;
            lastTx = lastTy = null;
            console.log( 'fire double tap event' );
          }
        }
        else{
          lastTx = endTx;
          lastTy = endTy;
        }
      }
      else{
        firstTouchEnd = true;
        lastTx = lastTy = null;
      }

      lastTime = now;
    }, false );

    // 在 iOS 的 safari 上手指敲擊屏幕的速度過快,
    // 有一定的幾率會導致第二次不會響應 touchstart 和 touchend 事件
    // 同時手指長時間的touch不會觸發click

    if( ~navigator.userAgent.toLowerCase().indexOf('iphone os') ){

      body.addEventListener( 'touchstart', function( e ){
          startTime = Date.now();
      }, true );

      body.addEventListener( 'touchend', function( e ){
          var noLongTap = Date.now() - startTime < 501;

          if( firstTouchEnd ){
              firstTouchEnd = false;
              if( noLongTap && e.target === element ){
                  dTapTimer = setTimeout(function(){
                      firstTouchEnd = true;
                      lastTx = lastTy = null;
                      console.log( 'fire double tap event' );
                  }, 400 );
              }
          }
          else{
              firstTouchEnd = true;
          }
      }, true );

    // iOS 上手指多次敲擊屏幕時的速度過快不會觸發 click 事件
    element.addEventListener( 'click', function( e ){
      if( dTapTimer ){
        clearTimeout( dTapTimer );
        dTapTimer = null;
        firstTouchEnd = true;
      }
    }, false );

    }

longTap事件

longTap 事件是當手指長時間按住屏幕保持不動時觸發的事件。


    var startTx, startTy, lTapTimer;

    element.addEventListener( 'touchstart', function( e ){
      if( lTapTimer ){
        clearTimeout( lTapTimer );
        lTapTimer = null;
      }

      var touches = e.touches[0];

      startTx = touches.clientX;
      startTy = touches.clientY;

      lTapTimer = setTimeout(function(){
        console.log( 'fire long tap event' );
      }, 1000 );

      e.preventDefault();
    }, false );

    element.addEventListener( 'touchmove', function( e ){
      var touches = e.touches[0],
        endTx = touches.clientX,
        endTy = touches.clientY;

      if( lTapTimer && (Math.abs(endTx - startTx) > 5 || Math.abs(endTy - startTy) > 5) ){
        clearTimeout( lTapTimer );
        lTapTimer = null;
      }
    }, false );

    element.addEventListener( 'touchend', function( e ){
      if( lTapTimer ){
        clearTimeout( lTapTimer );
        lTapTimer = null;
      }
    }, false );

swipe事件

swipe 事件是當手指在屏幕上滑動后觸發的事件,根據手指滑動的方向又分為 swipeLeft (向左)、swipeRight (向右)、swipeUp (向上)、swipeDown (向下)。


    var isTouchMove, startTx, startTy;

    element.addEventListener( 'touchstart', function( e ){
      var touches = e.touches[0];

      startTx = touches.clientX;
      startTy = touches.clientY;
      isTouchMove = false;
    }, false );

    element.addEventListener( 'touchmove', function( e ){
      isTouchMove = true;
      e.preventDefault();
    }, false );

    element.addEventListener( 'touchend', function( e ){
      if( !isTouchMove ){
        return;
      }

      var touches = e.changedTouches[0],
        endTx = touches.clientX,
        endTy = touches.clientY,
        distanceX = startTx - endTx
        distanceY = startTy - endTy,
        isSwipe = false;

      if( Math.abs(distanceX) >= Math.abs(distanceY) ){
        if( distanceX > 20 ){
          console.log( 'fire swipe left event' );
          isSwipe = true;
        }
        else if( distanceX < -20 ){
          console.log( 'fire swipe right event' );    
          isSwipe = true;
        }
      }
      else{
        if( distanceY > 20 ){
          console.log( 'fire swipe up event' );        
          isSwipe = true;
        }
        else if( distanceY < -20 ){
          console.log( 'fire swipe down event' );         
          isSwipe = true;
        }
      }

      if( isSwipe ){
        console.log( 'fire swipe event' );
      }
    }, false );

上面模擬的事件都封裝在 MonoEvent 中了。

demo 地址看這里,請使用移動設備訪問該地址,發現有問題歡迎反饋


所屬標簽

無標簽

25选5玩法中奖