移動端VUE點選、滑動和長按等事件處理(自定義指令)

yurt發表於2018-03-22

問題:

移動裝置上的觸控事件:touchstart、touchmove、touchend
如何利用它們三個來處理點選、長按、滑動等操作,以及如何在測試用例中模擬它們的操作

參考了:

  1. 實現方法上 https://blog.csdn.net/qq_1775… 這位大哥的思路
  2. 移動裝置的點選優化參考了MUI
//感謝!!!

解決

使用vue自定義指令來幹這件事

  1. touchstart來記錄開始點選的位置和時間,並在這裡邊判斷 長按 操作
  2. touchend來確定結束點選的位置和時間,來區分 點選 操作以及 不同方向的滑動 操作
  3. 需要有v-on一樣的修飾符來處理不同的觸發條件(.stop .prevent .self .once)
  4. 移動裝置上人類手指太粗導致的點選位置的問題的修正(使用Touch事件中的changeTouches來確定點選的中心位置)
  5. 移動裝置滑動方向的判斷(思路來自mui原始碼)

然後直接上操作類

class VueTouch {
  /**
   * @param el 繫結的DOM
   * @param binding 自定義指令中的binding物件
   * @param type 繫結的事件型別
   */
  constructor(el, binding, type) {
    
    let g = this;
    
    g.obj = el;
    g.binding = binding;
    g.touchType = type;
    
    g.firstTouchPosition = {x: 0, y: 0};
    g.firstTouchTime = 0;
    
   /**
     * =========================================
     * 事件繫結有兩種方式
     * @example
     *  1. v-tap="showDialog" 繫結一個方法物件
     *  2. v-tap="{fn:click123, param1:1, param2:2, param3:{aaa:`123`} ...}"
     *      繫結一個JSON字面量,fn是執行的方法,後邊的是需要傳遞的引數
     * 事件回撥引數
     * @param 第一個引數是event,事件物件
     * @param 第二個引數是 binding.value,也就是v-tap=""雙引號中的部分(如示例2,第二個引數就是 {fn:click123, param1:1, param2:2, param3:{aaa:`123`} ...})
     * =========================================
     */   
    g.callBack = typeof(binding.value) === "object" ? binding.value.fn : binding.value;
    
    // 事件監聽回撥集合
    let _listener = Object.create(null);
    
    // 事件方法
    let _listen = (type) => {
      return function (e) {
      
        /**
         * 取出修飾符(和v-on的一樣)
         */
        let {stop, prevent, self, once} = g.binding.modifiers;
        
        // 配置判斷
        if (stop) e.stopPropagation();
        if (prevent) e.preventDefault();
        if (once) g.obj.removeEventListener("touch" + type, _listener[type]);
        if (self && e.target !== e.currentTarget) return;
        
        g[type](e);
      }
    };
    
    _listener.start = _listen(`start`);
    _listener.end = _listen(`end`);
    _listener.move = _listen(`move`);
    
    // 事件繫結
    this.obj.addEventListener("touchstart", _listener.start, false);
    this.obj.addEventListener("touchend", _listener.end, false);
    this.obj.addEventListener("touchmove", _listener.move, false);
  }
  
  // 點選開始
  start(e) {
    
    let g = this;
    
    // @update 2018.3.26 這裡做了一個修改
    g.moved = false; //是否移動了
    g.leaved = false; //是否離開了 
    g.longTouched = false; //是否執行了長按操作
    
    // 獲取開始點選位置和時間
    g.firstTouchPosition = g.getMultiCenter(e.changedTouches);
    g.firstTouchTime = e.timeStamp;
    
    // 判斷長按操作 @TODO 稍微有點觸發時機上的問題,待修正
    g.time = setTimeout(function () {
      if (!g.leaved && !g.moved) {
        g.touchType === "longtap" && g.callBack(e, g.binding.value);
        g.longTouched = true;
      }
    }.bind(g), 1000);
  }
  
  // 點選結束
  end(e) {
    
    let g = this;
    
    // 點選結束獲取點選位置並計算位移和時差
    let {x, y} = g.getMultiCenter(e.changedTouches);
    let _disX = x - g.firstTouchPosition.x;
    let _disY = y - g.firstTouchPosition.y;
    let _dis = Math.sqrt(_disX * _disX + _disY * _disY);
    let _timeDis = e.timeStamp - g.firstTouchTime;
    
    clearTimeout(g.time);
    
    // 計算滑動角度
    let _angle = this.getAngle(_disX, _disY);
    
    // 判斷滑動方向
    if (_dis > 18 && _timeDis < 300) {
      g.touchType === "swipe" && g.callBack(e, g.binding.value);
      if (_angle >= 60 && _angle <= 120)
        g.touchType === "swipedown" && g.callBack(e, g.binding.value);
      if (_angle <= -60 && _angle >= -120)
        g.touchType === "swipeup" && g.callBack(e, g.binding.value);
      if (_angle <= 20 && _angle >= -20)
        g.touchType === "swipeleft" && g.callBack(e, g.binding.value);
      if ((_angle <= -160 && _angle > -180) || (_angle >= 160 && _angle <= 180))
        g.touchType === "swiperight" && g.callBack(e, g.binding.value);
    } else {
      if (!g.longTouched && !g.moved) {
        g.touchType === "tap" && g.callBack(e, g.binding.value);
        g.leaved = true;
      }
    }
  }
  
  move() {
    this.moved = true;
  }
  
  /**
   * 獲取點選集合的中心座標
   * @param touches touch物件集合
   * @return {{x: number, y: number}}
   */
  getMultiCenter(touches) {
    
    let g = this, x = 0, y = 0;
    
    const _length = touches.length;
    
    for (let i = 0; i < _length; i++) {
      x += touches[i].pageX;
      y += touches[i].pageY;
    }
    
    return {
      x: Math.round(x / _length),
      y: Math.round(y / _length)
    };
  };
  
  /**
   * 獲取滑動的角度
   * @param disX X軸的位移
   * @param disY Y軸的位移
   * @return 角度
   */
  getAngle(disX, disY) {
    let g = this;
    
    if (typeof disX !== `number` || typeof disY !== `number`)
      return 45;
    
    return Math.atan2(disY, disX) * 180 / Math.PI;
  }
}

然後執行生成指令

Vue.directive("tap", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "tap");
    }
  });
  Vue.directive("swipe", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "swipe");
    }
  });
  Vue.directive("swipeleft", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "swipeleft");
    }
  });
  Vue.directive("swiperight", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "swiperight");
    }
  });
  Vue.directive("swipedown", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "swipedown");
    }
  });
  Vue.directive("swipeup", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "swipeup");
    }
  });
  Vue.directive("longtap", {
    bind: function (el, binding) {
      new VueTouch(el, binding, "longtap");
    }
  });

原始碼參看:https://github.com/LylaYuKako…

相關文章