const DEFAULT_OPTIONS = { touchStart: function () {}, touchMove: function () {}, touchEnd: function () {}, touchCancel: function () {}, multipointStart: function () {}, multipointEnd: function () {}, tap: function () {}, doubleTap: function () {}, longTap: function () {}, singleTap: function () {}, rotate: function () {}, pinch: function () {}, pressMove: function () {}, swipe: function () {} }; export default class MinaTouch { constructor(_page, name, option = {}) { this.preV = { x: null, y: null }; this.pinchStartLen = null; this.scale = 1; this.isDoubleTap = false; this.delta = null; this.last = null; this.now = null; this.tapTimeout = null; this.singleTapTimeout = null; this.longTapTimeout = null; this.swipeTimeout = null; this.x1 = this.x2 = this.y1 = this.y2 = null; this.preTapPosition = { x: null, y: null }; this.lastZoom = 1; this.tempZoom = 1; try { if (this._checkBeforeCreate(_page, name)) { this._name = name; this._option = { ...DEFAULT_OPTIONS, ...option }; _page[name] = this; this._bindFunc(_page); } } catch (error) { console.error(error); } } _checkBeforeCreate(_page, name) { if (!_page || !name) { throw new Error('MinaTouch实例化时,必须传入page对象和引用名'); } if (_page[name]) { throw new Error('MinaTouch实例化error: ' + name + ' 已经存在page中'); } return true; } _bindFunc(_page) { let funcNames = ['start', 'move', 'end', 'cancel']; for (let funcName of funcNames) { _page[this._name + '.' + funcName] = this[funcName].bind(this); } } start(evt) { if (!evt.touches) { return; } this.now = Date.now(); if (evt.touches[0].pageX == null) { this.x1 = evt.touches[0].x; } else { this.x1 = evt.touches[0].pageX; } if (evt.touches[0].pageY == null) { this.y1 = evt.touches[0].y; } else { this.y1 = evt.touches[0].pageY; } this.delta = this.now - (this.last || this.now); this._option.touchStart(evt); if (this.preTapPosition.x !== null) { this.isDoubleTap = this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30; } this.preTapPosition.x = this.x1; this.preTapPosition.y = this.y1; this.last = this.now; let preV = this.preV; let len = evt.touches.length; if (len > 1) { this._cancelLongTap(); this._cancelSingleTap(); let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX; let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY; let v = { x: otx - this.x1, y: oty - this.y1 }; preV.x = v.x; preV.y = v.y; this.pinchStartLen = getLen(preV); this._option.multipointStart(evt); } this.longTapTimeout = setTimeout( function () { evt.type = 'longTap'; this._option.longTap(evt); }.bind(this), 750 ); } move(evt) { if (!evt.touches) { return; } let preV = this.preV; let len = evt.touches.length; let currentX = evt.touches[0].pageX == null ? evt.touches[0].x : evt.touches[0].pageX; let currentY = evt.touches[0].pageY == null ? evt.touches[0].y : evt.touches[0].pageY; this.isDoubleTap = false; if (len > 1) { // #ifdef MP-TOUTIAO if(!this.toutuaoMultipointStart){ this._cancelLongTap(); this._cancelSingleTap(); let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX; let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY; let v = { x: otx - currentX, y: oty - currentX }; preV.x = v.x; preV.y = v.y; this.pinchStartLen = getLen(preV); this._option.multipointStart(evt); this.toutuaoMultipointStart = true } // #endif let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX; let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY; let v = { x: otx - currentX, y: oty - currentY }; if (preV.x !== null) { if (this.pinchStartLen > 0) { evt.singleZoom = getLen(v) / this.pinchStartLen; evt.zoom = evt.singleZoom * this.lastZoom; this.tempZoom = evt.zoom; evt.type = 'pinch'; this._option.pinch(evt); } evt.angle = getRotateAngle(v, preV); evt.type = 'rotate'; this._option.rotate(evt); } preV.x = v.x; preV.y = v.y; } else { if (this.x2 !== null) { evt.deltaX = currentX - this.x2; evt.deltaY = currentY - this.y2; } else { evt.deltaX = 0; evt.deltaY = 0; } this._option.pressMove(evt); } this._option.touchMove(evt); this._cancelLongTap(); this.x2 = currentX; this.y2 = currentY; if (len > 1) { // evt.preventDefault(); } } end(evt) { if (!evt.changedTouches) { return; } this._cancelLongTap(); let self = this; evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2); //在结束钩子都加入方向判断,但触发swipe瞬时必须位移大于30 if (evt.touches.length < 2) { this.lastZoom = this.tempZoom; this._option.multipointEnd(evt); this.toutuaoMultipointStart = false } this._option.touchEnd(evt); //swipe if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) || (this.y2 && Math.abs(this.y1 - this.y2) > 30)) { // evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2); this.swipeTimeout = setTimeout(function () { evt.type = 'swipe'; self._option.swipe(evt); }, 0); } else { this.tapTimeout = setTimeout(function () { evt.type = 'tap'; self._option.tap(evt); // trigger double tap immediately if (self.isDoubleTap) { evt.type = 'doubleTap'; self._option.doubleTap(evt); clearTimeout(self.singleTapTimeout); self.isDoubleTap = false; } }, 0); if (!self.isDoubleTap) { self.singleTapTimeout = setTimeout(function () { self._option.singleTap(evt); }, 250); } } this.preV.x = 0; this.preV.y = 0; this.scale = 1; this.pinchStartLen = null; this.x1 = this.x2 = this.y1 = this.y2 = null; } cancel(evt) { clearTimeout(this.singleTapTimeout); clearTimeout(this.tapTimeout); clearTimeout(this.longTapTimeout); clearTimeout(this.swipeTimeout); this._option.touchCancel(evt); } _cancelLongTap() { clearTimeout(this.longTapTimeout); } _cancelSingleTap() { clearTimeout(this.singleTapTimeout); } _swipeDirection(x1, x2, y1, y2) { return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' : 'Down'; } destroy() { if (this.singleTapTimeout) { clearTimeout(this.singleTapTimeout); } if (this.tapTimeout) { clearTimeout(this.tapTimeout); } if (this.longTapTimeout) { clearTimeout(this.longTapTimeout); } if (this.swipeTimeout) { clearTimeout(this.swipeTimeout); } this._option.rotate = null; this._option.touchStart = null; this._option.multipointStart = null; this._option.multipointEnd = null; this._option.pinch = null; this._option.swipe = null; this._option.tap = null; this._option.doubleTap = null; this._option.longTap = null; this._option.singleTap = null; this._option.pressMove = null; this._option.touchMove = null; this._option.touchEnd = null; this._option.touchCancel = null; this.preV = this.pinchStartLen = this.scale = this.isDoubleTap = this.delta = this.last = this.now = this.tapTimeout = this.singleTapTimeout = this.longTapTimeout = this.swipeTimeout = this.x1 = this.x2 = this.y1 = this.y2 = this.preTapPosition = this.rotate = this.touchStart = this.multipointStart = this.multipointEnd = this.pinch = this.swipe = this.tap = this.doubleTap = this.longTap = this.singleTap = this.pressMove = this.touchMove = this.touchEnd = this.touchCancel = null; return null; } } function getLen(v) { return Math.sqrt(v.x * v.x + v.y * v.y); } function dot(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } function getAngle(v1, v2) { let mr = getLen(v1) * getLen(v2); if (mr === 0) { return 0; } let r = dot(v1, v2) / mr; if (r > 1) { r = 1; } return Math.acos(r); } function cross(v1, v2) { return v1.x * v2.y - v2.x * v1.y; } function getRotateAngle(v1, v2) { let angle = getAngle(v1, v2); if (cross(v1, v2) > 0) { angle *= -1; } return (angle * 180) / Math.PI; }