问题:
移动设备上的触摸事件:touchstart、touchmove、touchend
如何利用它们三个来处理点击、长按、滑动等操作,以及如何在测试用例中模拟它们的操作
参考了:
- 实现方法上 https://blog.csdn.net/qq_1775… 这位大哥的思路
- 移动设备的点击优化参考了MUI
//感谢!!!
解决
使用vue自定义指令来干这件事
- touchstart来记录开始点击的位置和时间,并在这里边判断 长按 操作
- touchend来确定结束点击的位置和时间,来区分 点击 操作以及 不同方向的滑动 操作
- 需要有v-on一样的修饰符来处理不同的触发条件(.stop .prevent .self .once)
- 移动设备上人类手指太粗导致的点击位置的问题的修正(使用Touch事件中的changeTouches来确定点击的中心位置)
- 移动设备滑动方向的判断(思路来自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");
}
});