var SUPPORT_TOUCH = "ontouchstart" in window; var touchEvents = [ ["touchstart", "start"], ["touchmove", "move"], ["touchend", "end"], ["touchcancel", "cancel"] ]; var mouseEvents = [ ["mousedown", "start"], ["mouseup", "end"], ["mousemove", "move"] ]; var events = SUPPORT_TOUCH ? touchEvents : mouseEvents; function forEach(obj, callback) { for (key in obj) { if (obj.hasOwnProperty(key)) callback(obj[key], key, obj); } } /* options{ leading:false 表示禁用第一次执行 trailing: false 表示禁用停止触发的回调 } */ function throttle(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : new Date().getTime(); timeout = null; func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = new Date().getTime(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; // 如果没有剩余的时间了或者你改了系统时间 if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } }; return throttled; } /* @params options : { string name function start , function move, function end, function cancel } */ function Gesture(options) { var _this = this; forEach(options, function(v, k) { _this[k] = v; }); if (this.init) this.init(); events.forEach(function(ev) { document.addEventListener( [ev[0]], function(e) { if (_this[ev[1]]) _this[ev[1]](e); }, false ); }); } Gesture.prototype = { pos: function(e, identifier) { var target = this.target(e, identifier); if (target) { return { x: target.clientX, y: target.clientY, identifier: identifier }; } else { return false; } }, target: function(e, identifier) { var target; if (SUPPORT_TOUCH) { if (typeof identifier === "number") { e.changedTouches.forEach(function(touch) { if (touch.identifier === identifier) target = touch; }); } else { target = e.changedTouches[0]; } } else { target = e; } return target; }, dispatch: function(e, name, detail) { e.target.dispatchEvent( new CustomEvent(name, { detail: detail, bubbles: true, cancelable: true }) ); } }; var tap = new Gesture({ name: "tap", init: function() { this.moved = false; this.startX = 0; this.startY = 0; this.number = 0; this.identifier = undefined; }, start: function(e) { if (SUPPORT_TOUCH && e.touches.length > 1) return; this.init(); var pos = this.pos(e); this.startX = pos.x; this.startY = pos.y; this.identifier = pos.identifier; }, move: function(e) { var pos = this.pos(e, this.identifier); if ( pos && (Math.abs(pos.x - this.startX) > 10 || Math.abs(pos.y - this.startY) > 10) ) { this.moved = true; } }, end: function(e) { if (this.target(e, this.identifier) && !this.moved) { this.dispatch(e, this.name, this.pos(e)); } }, cancel: function(e) { this.end(e) } }); var pan = new Gesture({ name: "pan", init: function() { this.isStart = false; this.startX = 0; this.startY = 0; this.prevX = 0; this.prevY = 0; this.prevT = 0; this.identifier = undefined; this.history = []; this.directionX = "none"; this.directionY = "none"; }, start: function(e) { if (SUPPORT_TOUCH && e.touches.length > 1) return; this.init(); var pos = this.pos(e); this.startX = this.prevX = pos.x; this.startY = this.prevY = pos.y; this.prevY = e.timeStamp; this.identifier = pos.identifier; }, move: function(e) { var pos = this.pos(e, this.identifier); if (pos) { var detail = this.detail(pos); // 方向相同且时间短记录,不然清空 if ( detail.directionX === this.directionX && e.timeStamp - this.prevT < 50 ) { if (this.history.length >= 10) this.history.shift(); } else { this.history = []; } this.history.push({ x: detail.x, y: detail.y, t: e.timeStamp, deltaX: detail.deltaX, deltaY: detail.deltaY }); if (!this.isStart) { this.isStart = true; this.dispatch(e, this.name + "start", detail); } this.throttleDispatch(e, this.name, detail) this.prevX = pos.x; this.prevY = pos.y; this.prevT = e.timeStamp; this.directionX = detail.directionX; this.directionY = detail.directionY; } }, end: function(e) { var pos = this.pos(e, this.identifier); var detail = this.detail(pos); detail.fast = this.history.length > 3 && e.timeStamp - this.prevT < 50 && Math.abs( (this.history[this.history.length - 1].x - this.history[0].x) / (this.history[this.history.length - 1].t - this.history[0].t) ) > 1; if (pos && this.isStart) this.dispatch(e, this.name + "end", detail); }, cancel: function(e) { this.end(e); }, detail: function(pos) { var deltaX = pos.x - this.startX, deltaY = pos.y - this.startY; if (this.prevX < pos.x) { var directionX = "right"; } else if (this.prevX > pos.x) { var directionX = "left"; } else { directionX = this.directionX; } if (this.prevY < pos.y) { var directionY = "bottom"; } else if (this.prevY > pos.y) { var directionY = "top"; } else { directionY = this.directionY; } return { x: pos.x, y: pos.y, deltaX: deltaX, deltaY: deltaY, directionY: directionY, directionX: directionX }; }, throttleDispatch: throttle(Gesture.prototype.dispatch,20,{ trailing: false }) });