鼠标/触屏 兼容的手势方案
(function(){ Module("Touchable",function(require,exports){ var hasTouch = 'ontouchstart' in document; var EvtType = {ON:hasTouch?"touchstart":"mousedown",OFF:hasTouch?"touchend":"mouseup",MOVE:hasTouch?"touchmove":"mousemove"} var CenterPoint; var THRESHOLD_MOVE = 5, THRESHOLD_TAP = 200, THRESHOLD_DOUBLETAP = 350, THRESHOLD_HOLD = 600, THRESHOLD_ANGLE = 10, THRESHOLD_ROTATE = 5, THRESHOLD_SCALE = 10, THRESHOLD_SWIPE = 10; function vt(x,y){ this.x=x; this.y=y; } vt.prototype.add=function(v){return new vt(this.x + v.x, this.y + v.y)} vt.prototype.minus=function(v){return new vt(this.x - v.x, this.y - v.y)} vt.prototype.dot=function(v){return this.x * v.x + this.y * v.y} vt.prototype.cross=function(v){return this.x * v.y - v.x * this.y} vt.prototype.mod=function(){return Math.sqrt(this.x * this.x + this.y * this.y)} vt.prototype.angle=function(v){ var angle,r,mr; mr = this.mod() * v.mod(); if(mr !== 0){ r = this.dot(v) / mr; if(r > 1) r = 1; angle=Math.acos(r); if(this.cross(v) > 0){ angle *= -1; } }else{ angle=0; } return Number((angle * 180 / Math.PI).toFixed(2)); } function toFloat(num,len){return Number((num||0).toFixed(len||2))} function getPoints(e){ var downList,upList; switch(e.type){ case EvtType.ON: case EvtType.MOVE: if(!hasTouch){ downList = [new vt(e.clientX,e.clientY)]; } else { downList = [].map.call(e.touches,function(e){return new vt(e.clientX,e.clientY)}); } upList = []; break; case EvtType.OFF: if(!hasTouch){ downList = []; upList = [new vt(e.clientX,e.clientY)]; } else { downList = [].map.call(e.touches,function(e){return new vt(e.clientX,e.clientY)}); upList = [].map.call(e.changedTouches,function(e){return new vt(e.clientX,e.clientY)}); } break; } if(e.ctrlKey) downList=[new vt(CenterPoint.clientX,CenterPoint.clientY)].concat(downList); return { 0:downList, 1:upList } } function Touchable(el,isPD){ var STATE={},p,pts,now,timestamp,last; CenterPoint = {clientX:(document.body.clientWidth)*0.5, clientY:(document.body.clientHeight)*0.5}; function handler(e){ isPD&&e.preventDefault(); switch(e.type){ case EvtType.ON: pts=getPoints(e); p={ "0":{}, "1":{}, "2":{} }; timestamp=(+new Date); STATE.count=pts[0].length; STATE.pressDown=1; switch(pts[0].length){ case 1: p[0][0]=p[1][0]=p[2][0]=pts[0][0]; trigger(e.target, 'pressdown', {}); setTimeout(function(){ if(STATE.count===1 && !STATE.moved){ STATE.hold = 1; trigger(e.target, 'hold', {"point":new vt(pts[0][0].x, pts[0][0].y)}); } }, THRESHOLD_HOLD); break; case 2: p[0][0]=p[1][0]=p[2][0]=pts[0][0]; p[0][1]=p[1][1]=p[2][1]=pts[0][1]; break; } break; case EvtType.MOVE: if(STATE.pressDown){ now=(+new Date); pts=getPoints(e); switch(pts[0].length){ case 1: var ds = pts[0][0].minus(p[0][0]).mod(); if(!STATE.hold) trigger(e.target, 'swiping', {"from":new vt(p[0][0].x, p[0][0].y), "point":new vt(pts[0][0].x, pts[0][0].y)}); if(!STATE.moved && ds > THRESHOLD_MOVE){STATE.moved=1} if(STATE.moved){ if(!STATE.hold && !STATE.swipe && ds > THRESHOLD_SWIPE){STATE.swipe=1} if(STATE.hold) trigger(e.target, 'drag', {"from":new vt(p[0][0].x, p[0][0].y), "point":new vt(pts[0][0].x, pts[0][0].y)}); } p[1][0]=p[2][0]; p[2][0]=pts[0][0]; break; case 2: var d1 = p[0][1].minus(p[0][0]).mod(); var d2 = pts[0][1].minus(pts[0][0]).mod(); var diff = d2 - d1; var ag = pts[0][1].minus(pts[0][0]).angle(p[0][1].minus(p[0][0])); if(!STATE.scale && Math.abs(diff) > THRESHOLD_SCALE){ STATE.move = 1; STATE.scale = 1; } if(STATE.scale){ trigger(e.target, 'scaling', {"scale":toFloat(d2/d1)}); } if(!STATE.rotate && d1 > 80 && (Math.abs(ag)>THRESHOLD_ROTATE)){ STATE.move = 1; STATE.rotate = 1; } if(STATE.rotate){ trigger(e.target, 'rotating', {"rotate":toFloat(ag)}); } p[1][0]=p[2][0]; p[2][0]=pts[0][0]; p[1][1]=p[2][1]; p[2][1]=pts[0][1]; break; } last=timestamp; timestamp=now; } break; case EvtType.OFF: if(STATE.pressDown){ STATE.pressDown = 0; pts=getPoints(e); var dt=(+new Date)-last; switch(pts[0].length){ case 1: var d0 = p[0][1].minus(p[0][0]).mod(); var d1 = p[1][1].minus(p[1][0]).mod(); var d2 = pts[1][1].minus(pts[1][0]).mod(); var ds = d2 - d1; var da = pts[1][1].minus(pts[1][0]).angle(p[1][1].minus(p[1][0])); var ag = pts[1][1].minus(pts[1][0]).angle(p[0][1].minus(p[0][0])); if(STATE.scale){ STATE.scale=0; p.scale=toFloat(d2/d0); trigger(e.target, 'scale', {scale:toFloat(d2/d0),speed:toFloat(ds/dt*1000)}); if(p.scale > 1){ trigger(e.target, 'scaleup', {}); } if(p.scale < 1){ trigger(e.target, 'scaledown', {}); } } if(STATE.rotate){ STATE.rotate=0; p.rotate=toFloat(ag); trigger(e.target, 'rotate', {rotate:toFloat(ag),speed:toFloat(da/dt*1000)}); if(p.rotate < -45 || p.rotate > 315){ trigger(e.target, 'rotateright', {}); } if(p.rotate > 45 && p.rotate < 315){ trigger(e.target, 'rotateleft', {}); } } break; case 0: if(!STATE.moved){ if(STATE.hold){ STATE.hold = 0; trigger(e.target, 'drop', {"point":new vt(pts[1][0].x, pts[1][0].y)}); }else{ if(STATE.doubleReady){ STATE.doubleReady = 0; trigger(e.target, 'doubletap', {"point":new vt(pts[1][0].x, pts[1][0].y)}); }else if(STATE.count===1){ STATE.doubleReady = 1; setTimeout(function(){if(STATE.doubleReady) trigger(e.target, 'tap', {"point":new vt(pts[1][0].x, pts[1][0].y)})}, THRESHOLD_TAP) setTimeout(function(){STATE.doubleReady=0}, THRESHOLD_DOUBLETAP); } } }else{ var dx = pts[1][0].x - p[1][0].x; var dy = pts[1][0].y - p[1][0].y; if(STATE.hold){ STATE.hold = 0; trigger(e.target, 'drop', {"point":new vt(pts[1][0].x, pts[1][0].y)}); }else{ var angle = pts[1][0].minus(p[0][0]).angle(new vt(1,0)); trigger(e.target, 'swipe', {"angle":toFloat(angle),"speed":{x:toFloat(dx/dt*1000),y:toFloat(dy/dt*1000)}}); if(Math.abs(angle - 360) < THRESHOLD_ANGLE || Math.abs(angle - 0) < THRESHOLD_ANGLE){trigger(e.target, 'swiperight', {});} else if(Math.abs(angle - 90) < THRESHOLD_ANGLE){trigger(e.target, 'swipeup', {});} else if(Math.abs(angle - 180) < THRESHOLD_ANGLE){trigger(e.target, 'swipeleft', {});} else if(Math.abs(angle - 270) < THRESHOLD_ANGLE){trigger(e.target, 'swipedown', {});} } } break; } STATE.count=0; STATE.moved=0; } break; } } function trigger(el,name,data){ setTimeout(function(){ var evt = document.createEvent("CustomEvent"); evt.initCustomEvent(name, true, true, data); el.dispatchEvent(evt); },0) } function bind(){ if(hasTouch){ el.addEventListener("touchstart", handler, false); el.addEventListener("touchmove", handler, false); el.addEventListener("touchend", handler, false); } else { el.addEventListener("mousedown", handler, false); el.addEventListener("mousemove", handler, false); el.addEventListener("mouseup", handler, false); } } bind(); } exports(Touchable); }); })();//gesture
模块化方案使用Module
代码参考了AlloyFinger
事件所带的参数一直很纠结,犹豫不定哪些是必须的,哪些是不需要的
(function(){Module("Touchable",function(require,exports){var hasTouch = 'ontouchstart' in document;var EvtType = {ON:hasTouch?"touchstart":"mousedown",OFF:hasTouch?"touchend":"mouseup",MOVE:hasTouch?"touchmove":"mousemove"}var CenterPoint;var THRESHOLD_MOVE = 5,THRESHOLD_TAP = 200,THRESHOLD_DOUBLETAP = 350,THRESHOLD_HOLD = 600,THRESHOLD_ANGLE = 10,THRESHOLD_ROTATE = 5,THRESHOLD_SCALE = 10,THRESHOLD_SWIPE = 10;
function vt(x,y){this.x=x;this.y=y;}vt.prototype.add=function(v){return new vt(this.x + v.x, this.y + v.y)}vt.prototype.minus=function(v){return new vt(this.x - v.x, this.y - v.y)}vt.prototype.dot=function(v){return this.x * v.x + this.y * v.y}vt.prototype.cross=function(v){return this.x * v.y - v.x * this.y}vt.prototype.mod=function(){return Math.sqrt(this.x * this.x + this.y * this.y)}vt.prototype.angle=function(v){var angle,r,mr;mr = this.mod() * v.mod();if(mr !== 0){r = this.dot(v) / mr;if(r > 1) r = 1;angle=Math.acos(r);if(this.cross(v) > 0){angle *= -1;}}else{angle=0;}return Number((angle * 180 / Math.PI).toFixed(2));}
function toFloat(num,len){return Number((num||0).toFixed(len||2))}function getPoints(e){var downList,upList;switch(e.type){case EvtType.ON:case EvtType.MOVE:if(!hasTouch){downList = [new vt(e.clientX,e.clientY)];} else {downList = [].map.call(e.touches,function(e){return new vt(e.clientX,e.clientY)});}upList = [];break;case EvtType.OFF:if(!hasTouch){downList = [];upList = [new vt(e.clientX,e.clientY)];} else {downList = [].map.call(e.touches,function(e){return new vt(e.clientX,e.clientY)});upList = [].map.call(e.changedTouches,function(e){return new vt(e.clientX,e.clientY)});}break;}if(e.ctrlKey) downList=[new vt(CenterPoint.clientX,CenterPoint.clientY)].concat(downList);return {0:downList,1:upList}}
function Touchable(el,isPD){var STATE={},p,pts,now,timestamp,last;CenterPoint = {clientX:(document.body.clientWidth)*0.5, clientY:(document.body.clientHeight)*0.5};
function handler(e){isPD&&e.preventDefault();switch(e.type){case EvtType.ON:pts=getPoints(e);p={"0":{},"1":{},"2":{}};timestamp=(+new Date);STATE.count=pts[0].length;STATE.pressDown=1;switch(pts[0].length){case 1:p[0][0]=p[1][0]=p[2][0]=pts[0][0];trigger(e.target, 'pressdown', {});setTimeout(function(){if(STATE.count===1 && !STATE.moved){STATE.hold = 1;trigger(e.target, 'hold', {"point":new vt(pts[0][0].x, pts[0][0].y)});}}, THRESHOLD_HOLD);break;case 2:p[0][0]=p[1][0]=p[2][0]=pts[0][0];p[0][1]=p[1][1]=p[2][1]=pts[0][1];break;}break;case EvtType.MOVE:if(STATE.pressDown){now=(+new Date);pts=getPoints(e);switch(pts[0].length){case 1:var ds = pts[0][0].minus(p[0][0]).mod();if(!STATE.hold) trigger(e.target, 'swiping', {"from":new vt(p[0][0].x, p[0][0].y), "point":new vt(pts[0][0].x, pts[0][0].y)});if(!STATE.moved && ds > THRESHOLD_MOVE){STATE.moved=1}if(STATE.moved){if(!STATE.hold && !STATE.swipe && ds > THRESHOLD_SWIPE){STATE.swipe=1}if(STATE.hold) trigger(e.target, 'drag', {"from":new vt(p[0][0].x, p[0][0].y), "point":new vt(pts[0][0].x, pts[0][0].y)});}p[1][0]=p[2][0];p[2][0]=pts[0][0];break;case 2:var d1 = p[0][1].minus(p[0][0]).mod();var d2 = pts[0][1].minus(pts[0][0]).mod();var diff = d2 - d1;var ag = pts[0][1].minus(pts[0][0]).angle(p[0][1].minus(p[0][0]));if(!STATE.scale && Math.abs(diff) > THRESHOLD_SCALE){STATE.move = 1;STATE.scale = 1;}if(STATE.scale){trigger(e.target, 'scaling', {"scale":toFloat(d2/d1)});}if(!STATE.rotate && d1 > 80 && (Math.abs(ag)>THRESHOLD_ROTATE)){STATE.move = 1;STATE.rotate = 1;}if(STATE.rotate){trigger(e.target, 'rotating', {"rotate":toFloat(ag)});}p[1][0]=p[2][0];p[2][0]=pts[0][0];p[1][1]=p[2][1];p[2][1]=pts[0][1];break;}last=timestamp;timestamp=now;}break;case EvtType.OFF:if(STATE.pressDown){STATE.pressDown = 0;pts=getPoints(e);var dt=(+new Date)-last;switch(pts[0].length){case 1:var d0 = p[0][1].minus(p[0][0]).mod();var d1 = p[1][1].minus(p[1][0]).mod();var d2 = pts[1][1].minus(pts[1][0]).mod();var ds = d2 - d1;var da = pts[1][1].minus(pts[1][0]).angle(p[1][1].minus(p[1][0]));var ag = pts[1][1].minus(pts[1][0]).angle(p[0][1].minus(p[0][0]));if(STATE.scale){STATE.scale=0;p.scale=toFloat(d2/d0);trigger(e.target, 'scale', {scale:toFloat(d2/d0),speed:toFloat(ds/dt*1000)});if(p.scale > 1){ trigger(e.target, 'scaleup', {});}if(p.scale < 1){ trigger(e.target, 'scaledown', {});}}if(STATE.rotate){STATE.rotate=0;p.rotate=toFloat(ag);trigger(e.target, 'rotate', {rotate:toFloat(ag),speed:toFloat(da/dt*1000)});if(p.rotate < -45 || p.rotate > 315){ trigger(e.target, 'rotateright', {});}if(p.rotate > 45 && p.rotate < 315){ trigger(e.target, 'rotateleft', {});}}break;case 0:if(!STATE.moved){if(STATE.hold){STATE.hold = 0;trigger(e.target, 'drop', {"point":new vt(pts[1][0].x, pts[1][0].y)});}else{if(STATE.doubleReady){STATE.doubleReady = 0;trigger(e.target, 'doubletap', {"point":new vt(pts[1][0].x, pts[1][0].y)});}else if(STATE.count===1){STATE.doubleReady = 1;setTimeout(function(){if(STATE.doubleReady) trigger(e.target, 'tap', {"point":new vt(pts[1][0].x, pts[1][0].y)})}, THRESHOLD_TAP)setTimeout(function(){STATE.doubleReady=0}, THRESHOLD_DOUBLETAP);}}}else{var dx = pts[1][0].x - p[1][0].x;var dy = pts[1][0].y - p[1][0].y;if(STATE.hold){STATE.hold = 0;trigger(e.target, 'drop', {"point":new vt(pts[1][0].x, pts[1][0].y)});}else{var angle = pts[1][0].minus(p[0][0]).angle(new vt(1,0));trigger(e.target, 'swipe', {"angle":toFloat(angle),"speed":{x:toFloat(dx/dt*1000),y:toFloat(dy/dt*1000)}});if(Math.abs(angle - 360) < THRESHOLD_ANGLE || Math.abs(angle - 0) < THRESHOLD_ANGLE){trigger(e.target, 'swiperight', {});}else if(Math.abs(angle - 90) < THRESHOLD_ANGLE){trigger(e.target, 'swipeup', {});}else if(Math.abs(angle - 180) < THRESHOLD_ANGLE){trigger(e.target, 'swipeleft', {});}else if(Math.abs(angle - 270) < THRESHOLD_ANGLE){trigger(e.target, 'swipedown', {});}}}break;}STATE.count=0;STATE.moved=0;}break;}}function trigger(el,name,data){setTimeout(function(){var evt = document.createEvent("CustomEvent");evt.initCustomEvent(name, true, true, data);el.dispatchEvent(evt);},0)}function bind(){if(hasTouch){el.addEventListener("touchstart", handler, false);el.addEventListener("touchmove", handler, false);el.addEventListener("touchend", handler, false);} else {el.addEventListener("mousedown", handler, false);el.addEventListener("mousemove", handler, false);el.addEventListener("mouseup", handler, false);}}bind();}
exports(Touchable);});})();//gesture

浙公网安备 33010602011771号