vue 处理移动设备上的 点击、长按、左右上下滑动 事件


2019-7-20 vue
/* eslint-disable no-new */
/* eslint-disable no-underscore-dangle */
/**
 * 处理 移动设备上的 点击、长按、左右上下滑动 事件
 *
 * =========================================
 * 导出了7个自定义指令:
 *  v-tap: tap点击事件
 *  v-swipe: swipe滑动事件
 *  v-swipeleft: swipeleft左滑事件
 *  v-swiperight: swiperight右滑事件
 *  v-swipedown: swipedown下滑
 *  v-swipeup: swipeup上滑
 *  v-longtap: longtap长按
 * =========================================
 *
 * =========================================
 * 包含四个参数
 * stop 阻止冒泡
 * prevent 阻止默认事件
 * self 只当在 event.target 是当前元素自身时触发处理函数
 * once 执行一次后解绑
 *
 * @example v-tap.stop.prevent.self.once
 *
 *事件绑定有两种方式
 * @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'} ...})
 * =========================================
 *
 * @update
 *  1. 根据MUI进行了滑动事件的判断修正
 *  2. 根据TouchEvent修正了点击位置的判断
 *
 */

import Vue from 'vue';

/**
 * vue上点击事件处理类
 */
class VueTouch {
  /**
   * @param el
   * @param binding
   * @param type
   */
  constructor(el, binding, type) {
    const that = this;

    that.obj = el;
    that.binding = binding;
    that.touchType = type;

    that.firstTouchPosition = { x: 0, y: 0 };
    that.firstTouchTime = 0;
    that.callBack = typeof (binding.value) === 'object' ? binding.value.fn : binding.value;

    that.moved = false;
    that.leaved = false;
    that.longTouched = false;

    // 事件监听回调集合
    const _listener = Object.create(null);

    // 事件方法
    const _listen = _type => (e) => {
      const {
        stop, prevent, self, once,
      } = that.binding.modifiers;

      // 配置判断
      if (stop) {
        e.stopPropagation();
      }
      if (prevent) {
        e.preventDefault();
      }
      if (once) {
        that.obj.removeEventListener(`touch${_type}`, _listener[_type]);
      }
      if (self && e.target !== e.currentTarget) {
        return;
      }

      that[_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) {
    const that = this;

    // 初始化点击状态
    that.moved = false;
    that.leaved = false;
    that.longTouched = false;

    that.firstTouchPosition = that.getMultiCenter(e.changedTouches);
    that.firstTouchTime = e.timeStamp;
    that.timer = setTimeout((() => {
      if (!that.leaved && !that.moved) {
        if (that.touchType === 'longtap') {
          that.callBack(e, that.binding.value);
        }
        that.longTouched = true;
      }
    }), 1000);
  }

  end(e) {
    const that = this;

    const { x, y } = that.getMultiCenter(e.changedTouches);
    const _disX = x - that.firstTouchPosition.x;
    const _disY = y - that.firstTouchPosition.y;
    const _dis = Math.sqrt(_disX * _disX + _disY * _disY);
    const _timeDis = e.timeStamp - that.firstTouchTime;

    clearTimeout(that.timer);

    const _angle = this.getAngle(_disX, _disY);

    if (_dis > 18 && _timeDis < 300) {
      if (that.touchType === 'swipe') {
        that.callBack(e, that.binding.value);
      }
      if (_angle >= 60 && _angle <= 120) {
        if (that.touchType === 'swipedown') {
          that.callBack(e, that.binding.value);
        }
      }
      if (_angle <= -60 && _angle >= -120) {
        if (that.touchType === 'swipeup') {
          that.callBack(e, that.binding.value);
        }
      }
      if (_angle <= 20 && _angle >= -20) {
        if (that.touchType === 'swipeleft') {
          that.callBack(e, that.binding.value);
        }
      }
      if ((_angle <= -160 && _angle > -180) || (_angle >= 160 && _angle <= 180)) {
        if (that.touchType === 'swiperight') {
          that.callBack(e, that.binding.value);
        }
      }
    } else if (!that.longTouched && !that.moved) {
      if (that.touchType === 'tap') {
        that.callBack(that, that.binding.value);
      }
      that.leaved = true;
    }
  }

  move() {
    this.moved = true;
  }

  /**
   * 获取点击集合的中心坐标
   * @param touches touch对象集合
   * @return {{x: number, y: number}}
   */
  getMultiCenter(touches) {
    const that = this;
    let x = 0;
    let 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),
    };
  }

  getAngle(disX, disY) {
    const that = this;

    if (typeof disX !== 'number' || typeof disY !== 'number') { return 45; }

    return Math.atan2(disY, disX) * 180 / Math.PI;
  }
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
Thomas: 10/4/2019, 10:47:01 AM