javascript 常用手势, 个人觉得有3个 tap,swipe(swipeLeft,swipeRight,swipeTop,swipeRight),hold

tap 是轻击 判断的原则是,在toustart后,移动范围不超过10px(圆的范围),就算是 轻击了

swipe 是轻滑(轻扫) 判断在toustart后,时间间隔小于300ms,移动范围大于20,就判断是轻滑

hold(常按) 按住 移动范围小于10px,时间大于200ms,就认为他是hold

自定义手势,网上相关的源码很多,我也找了一个来研究,叫touch.js,挺不错的支持pc端,移动端(移动端就是touchstart,pc端就是mousedown),虽然有些小bug,比如事件删除有问题

touch.js的地址 http://touch.code.baidu.com/

一些要准备的基础

手势的基本实现原理

阉割源码解析

支持移动端 pc端的阉割源码解析

zepto的手势源码解析

一些我遇到的手势问题

一些要准备的基础

1.对touch相关的东西要了解

指尖上的js是很好的东西呀

指尖上的js一

指尖上的js二

指尖上的js三

2.自定义事件CustomEvent

dom是添加自定义事件的,也可以触发它,它还以冒泡

火狐的一个官方说明 官方说明

一篇比较详细的介绍,还有例子 点点点

自定义事件是可以用chrome看到的,如图

手势的基本实现原理

tap,hold,swipe都是js没有的事件,都是由,touchstart,touchmove,touchend touchcancel这些事件组合而成的

实现原理就是通过绑定document的”touchstart touchmove touchend touchcancel“事件

当touch到元素,查看手势是否符合tap,swipe的原则

如果符合原则,就触发元素绑定的相关事件

判断移动了多少位置

比如我点击了元素a,我就就得记下点击时的位置,计算方式如下

touches[0].pageX,touches[0].pageY

这个是手指点击的位置离页面顶端的位置(或者是页面的左边)

然后再touchmove时记下相关的位置

在touchend或者touchcancel时,用2个数据,算一下移动了多少就行了

ps:touchend和touchcancel是没有event的所以必须在touchmove里面记录位置

阉割源码解析

touch.js 以我的水平来看,并不能很流畅的阅读源码...

而且有些手势也不是很常用,做了些阉割,写了些注释,方便理解

<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>wo ca!~</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
</head>
<style>
.xx{width: 200px;background: #ccc; height: 100px;}
</style>
<body>
<div id="vv" class="xx"></div>
<br>
<div id="ss" class="xx"></div>
<br>
<div id="ss1" class="xx">1</div>
<br>
<div id="ss2" class="xx">1</div>
<br>
<div id="ss3" class="xx"></div>
<br>
<div id="ss4" class="xx"></div> <script>
(function(){
var utils = {};
//获取元素的点击位置
utils.getPosOfEvent = function(ev){
var posi = [];
var src = null;
for (var t = 0, len = ev.touches.length; t < len; t++) {
src = ev.touches[t];
posi.push({
x: src.pageX,
y: src.pageY
});
}
return posi;
}
utils.getType = function(obj) {
return Object.prototype.toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
};
//获取点击的手指数量
utils.getFingers = function(ev) {
return ev.touches ? ev.touches.length : 1;
};
utils.isTouchMove = function(ev) {
return ev.type === 'touchmove';
};
//是否已经结束了手势
utils.isTouchEnd = function(ev) {
return (ev.type === 'touchend' || ev.type === 'touchcancel');
};
//算2点之间的距离
utils.getDistance = function(pos1, pos2) {
var x = pos2.x - pos1.x,
y = pos2.y - pos1.y;
return Math.sqrt((x * x) + (y * y));
};
//算角度
utils.getAngle = function(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
};
//根据角度 返回up down left right
utils.getDirectionFromAngle = function(agl) {
var directions = {
up: agl < -45 && agl > -135,
down: agl >= 45 && agl < 135,
left: agl >= 135 || agl <= -135,
right: agl >= -45 && agl <= 45
};
for (var key in directions) {
if (directions[key]) return key;
}
return null;
};
utils.reset = function() {
startEvent = moveEvent = endEvent = null;
__tapped = __touchStart = startSwiping = false;
pos = {start: null,move: null,end: null};
}; //ua
utils.env = (function() {
var os = {}, ua = navigator.userAgent,
android = ua.match(/(Android)[\s\/]+([\d\.]+)/),
ios = ua.match(/(iPad|iPhone|iPod)\s+OS\s([\d_\.]+)/),
wp = ua.match(/(Windows\s+Phone)\s([\d\.]+)/),
isWebkit = /WebKit\/[\d.]+/i.test(ua),
isSafari = ios ? (navigator.standalone ? isWebkit : (/Safari/i.test(ua) && !/CriOS/i.test(ua) && !/MQQBrowser/i.test(ua))) : false;
if (android) {
os.android = true;
os.version = android[2];
}
if (ios) {
os.ios = true;
os.version = ios[2].replace(/_/g, '.');
os.ios7 = /^7/.test(os.version);
if (ios[1] === 'iPad') {
os.ipad = true;
} else if (ios[1] === 'iPhone') {
os.iphone = true;
os.iphone5 = screen.height == 568;
} else if (ios[1] === 'iPod') {
os.ipod = true;
}
}
if (isWebkit) {
os.webkit = true;
}
if (isSafari) {
os.safari = true;
}
return os;
})(); //已配置 tap hold swipe表示是否开启手势
//tapTime tap事件延迟触发的时间
//holdTime hold事件多少秒后触发
//tapMaxDistance 触发tap的时候 最小的移动范围
//swipeMinDistance 触发swipe的时候 最小的移动范围
//swipeTime touchstart 到touchend之前的时间 如果小于swipeTime 才会触发swipe手势
var config = {
tap: true,
tapMaxDistance: 10,
hold: true,
tapTime: 200,
holdTime: 650,
swipe: true,
swipeTime: 300,
swipeMinDistance: 18
};
var smrEventList = {
TOUCH_START: 'touchstart',
TOUCH_MOVE: 'touchmove',
TOUCH_END: 'touchend',
TOUCH_CANCEL: 'touchcancel',
SWIPE_START: 'swipestart',
SWIPING: 'swiping',
SWIPE_END: 'swipeend',
SWIPE_LEFT: 'swipeleft',
SWIPE_RIGHT: 'swiperight',
SWIPE_UP: 'swipeup',
SWIPE_DOWN: 'swipedown',
SWIPE: 'swipe',
HOLD: 'hold',
TAP: 'tap',
};
/** 手势识别 */
//记录 开始 移动 结束时候的位置
var pos = {
start: null,
move: null,
end: null
};
var __touchStart = true;
var __tapped;
var __prev_tapped_end_time;
var __prev_tapped_pos;
var __holdTimer = null;
var startTime;
var startEvent;
var moveEvent;
var endEvent;
var startSwiping; var gestures = {
swipe: function(ev) {
var el = ev.target;
if (!__touchStart || !pos.move || utils.getFingers(ev) > 1) {
return;
}
//计算 时间 距离 角度
var now = Date.now();
var touchTime = now - startTime;
var distance = utils.getDistance(pos.start[0], pos.move[0]);
var angle = utils.getAngle(pos.start[0], pos.move[0]);
var direction = utils.getDirectionFromAngle(angle);
var touchSecond = touchTime / 1000;
var eventObj = {
type: smrEventList.SWIPE,
originEvent: ev,
direction: direction,
distance: distance,
distanceX: pos.move[0].x - pos.start[0].x,
distanceY: pos.move[0].y - pos.start[0].y,
x: pos.move[0].x - pos.start[0].x,
y: pos.move[0].y - pos.start[0].y,
angle: angle,
duration: touchTime,
fingersCount: utils.getFingers(ev)
};
if (config.swipe) {
var swipeTo = function() {
var elt = smrEventList;
switch (direction) {
case 'up':
engine.trigger(el, elt.SWIPE_UP, eventObj);
break;
case 'down':
engine.trigger(el, elt.SWIPE_DOWN, eventObj);
break;
case 'left':
engine.trigger(el, elt.SWIPE_LEFT, eventObj);
break;
case 'right':
engine.trigger(el, elt.SWIPE_RIGHT, eventObj);
break;
}
};
if (!startSwiping) {
eventObj.fingerStatus = eventObj.swipe = 'start';
//大于tap的最小距离 才算进入swipe手势
if(distance>config.tapMaxDistance){
startSwiping = true;
}
} else if (utils.isTouchMove(ev)) {
eventObj.fingerStatus = eventObj.swipe = 'move';
engine.trigger(el, smrEventList.SWIPING, eventObj);
} else if (utils.isTouchEnd(ev)) {
eventObj.fingerStatus = eventObj.swipe = 'end';
//事件要短 距离要有点远
if (config.swipeTime > touchTime && distance > config.swipeMinDistance) {
swipeTo();
engine.trigger(el, smrEventList.SWIPE, eventObj, false);
}
}
}
},
tap : function(ev){
var el = ev.target;
//如果设置了tap为true 才会触发该手势
if (config.tap) {
var now = Date.now();
var touchTime = now - startTime;
var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
clearTimeout(__holdTimer);
//如果移动的距离比设置的距离大(10) 就不算是tap
if (config.tapMaxDistance < distance) return; __tapped = true;
__prev_tapped_end_time = now;
__prev_tapped_pos = pos.start[0];
__tapTimer = setTimeout(function() {
engine.trigger(el, smrEventList.TAP, {
type: smrEventList.TAP,
originEvent: ev
});
},
config.tapTime);
}
},
hold: function(ev) {
var el = ev.target;
//如果设置了hold为true 才会触发该手势
if (config.hold) {
clearTimeout(__holdTimer);
__holdTimer = setTimeout(function() {
if (!pos.start) return;
var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
//如果移动的距离大于配置的距离(10) 就不触发hold
if (config.tapMaxDistance < distance) return; if (!__tapped) {
engine.trigger(el, "hold", {
type: 'hold',
originEvent: ev,
fingersCount: utils.getFingers(ev),
position: pos.start[0]
});
}
},
config.holdTime);
}
}
} /** 底层事件绑定/代理支持 */
var engine = {
proxyid: 0,
proxies: [],
trigger : function(el, evt, detail){
detail = detail || {};
var e, opt = {
bubbles: true,
cancelable: true,
detail: detail
};
try {
//这里是触发 自定义事件
if (typeof CustomEvent !== 'undefined') {
e = new CustomEvent(evt, opt);
if (el) {
el.dispatchEvent(e);
}
} else {
e = document.createEvent("CustomEvent");
e.initCustomEvent(evt, true, true, detail);
if (el) {
el.dispatchEvent(e);
}
}
} catch (ex) {
console.warn("Touch.js is not supported by environment.");
}
},
bind: function(el, evt, handler) {
el.listeners = el.listeners || {};
//proxy才是真正元素绑定的事件
var proxy = function(e) {
//对ios7的一个兼容 也不知道是什么原理 if (utils.env.ios7) {
utils.forceReflow();
} e.originEvent = e;
for (var p in e.detail) {
if (p !== 'type') {
e[p] = e.detail[p];
}
}
var returnValue = handler.call(e.target, e);
if (typeof returnValue !== "undefined" && !returnValue) {
e.stopPropagation();
e.preventDefault();
}
}; if (!el.listeners[evt]) {
el.listeners[evt] = [proxy];
} else {
el.listeners[evt].push(proxy);
} handler.proxy = handler.proxy || {};
if (!handler.proxy[evt]) {
handler.proxy[evt] = [this.proxyid++];
} else {
handler.proxy[evt].push(this.proxyid++);
}
this.proxies.push(proxy);
if (el.addEventListener) {
el.addEventListener(evt, proxy, false);
}
},
unbind : function(el, evt){
var handlers = el.listeners[evt];
if (handlers && handlers.length) {
handlers.forEach(function(handler) {
el.removeEventListener(evt, handler, false);
});
}
}
} var _on = function(el,evt,handler) {
//绑定事件 支持多元素 多事件绑定噢
var evts = evt.split(" ");
var els = utils.getType(el) === 'string' ? document.querySelectorAll(el) : [el]; evts.forEach(function(evt) {
for(var i=0,len=els.length;i<len;i++){
engine.bind(els[i], evt, handler);
}
});
}; var _off = function(els,evts,handler) {
//删除绑定事件 支持多元素 多事件删除绑定噢
var els = utils.getType(els) === 'string' ? document.querySelectorAll(els) : els;
els = els.length ? Array.prototype.slice.call(els) : [els];
els.forEach(function(el) {
evts = evts.split(" ");
evts.forEach(function(evt) {
engine.unbind(el, evt, handler);
});
});
return;
}; //这个函数很重要
// doucment的触屏事件全部在这个里面
var handlerOriginEvent = function(ev) {
var el = ev.target;
switch (ev.type) {
case 'touchstart':
//记录下刚开始点击的事件和位置
__touchStart = true;
if (!pos.start || pos.start.length < 2) {
pos.start = utils.getPosOfEvent(ev);
}
startTime = Date.now();
startEvent = ev;
gestures.hold(ev);
break;
case 'touchmove':
if (!__touchStart || !pos.start) return;
//记录滑动过程中的位置
pos.move = utils.getPosOfEvent(ev);
gestures.swipe(ev);
break;
case 'touchend':
case 'touchcancel':
if (!__touchStart) return;
endEvent = ev;
//.......
if (startSwiping) {
gestures.swipe(ev);
} else {
gestures.tap(ev);
} utils.reset();
if (ev.touches && ev.touches.length === 1) {
__touchStart = false;
}
break;
}
} var init = function(){
//给 document 绑定 下面这些事件
var touchEvents = 'touchstart touchmove touchend touchcancel';
touchEvents.split(" ").forEach(function(evt) {
document.addEventListener(evt, handlerOriginEvent, false);
});
}
init();
window.touch = {
on : _on,
off : _off
};
})(); touch.on("#vv","tap",function(){
ss1.innerHTML = ~~ss1.innerHTML+1;
}); touch.on("#vv","swipeleft",function(){
ss2.innerHTML = ~~ss2.innerHTML+1;
});
touch.on("#vv","swiperight",function(){
ss2.innerHTML = ~~ss2.innerHTML-1;
}); touch.on("#vv","hold",function(){
ss.innerHTML = ~~ss.innerHTML+1;
});
</script>
</body>
</html>

支持移动端 pc端的阉割源码解析

taobao的页面在ipad上,图片的轮询是支持手势滑动的,天涯的也一样,在pc就支持click了,所以手势封装也是支持pc和移动端才好

<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>wo ca!~</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
</head>
<style>
.xx{width: 200px;background: #ccc; height: 100px;}
</style>
<body>
<div id="vv" class="xx"></div>
<br>
<div id="ss" class="xx"></div>
<br>
<div id="ss1" class="xx">1</div>
<br>
<div id="ss2" class="xx">1</div>
<br>
<div id="ss3" class="xx"></div>
<br>
<div id="ss4" class="xx"></div> <script>
(function(){
var utils = {};
//获取元素的点击位置
utils.getPosOfEvent = function(ev){
if (this.hasTouch) {
var posi = [];
var src = null; for (var t = 0, len = ev.touches.length; t < len; t++) {
src = ev.touches[t];
posi.push({
x: src.pageX,
y: src.pageY
});
}
return posi;
} else {
return [{
x: ev.pageX,
y: ev.pageY
}];
}
}
utils.hasTouch = ('ontouchstart' in window);
utils.PCevts = {
'touchstart': 'mousedown',
'touchmove': 'mousemove',
'touchend': 'mouseup',
'touchcancel': 'mouseout'
};
utils.getPCevts = function(evt) {
return this.PCevts[evt] || evt;
};
utils.getType = function(obj) {
return Object.prototype.toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
};
//获取点击的手指数量
utils.getFingers = function(ev) {
return ev.touches ? ev.touches.length : 1;
};
utils.isTouchMove = function(ev) {
return (ev.type === 'touchmove' || ev.type === 'mousemove');
};
//是否已经结束了手势
utils.isTouchEnd = function(ev) {
return (ev.type === 'touchend' || ev.type === 'mouseup' || ev.type === 'touchcancel');
};
//算2点之间的距离
utils.getDistance = function(pos1, pos2) {
var x = pos2.x - pos1.x,
y = pos2.y - pos1.y;
return Math.sqrt((x * x) + (y * y));
};
//算角度
utils.getAngle = function(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
};
//根据角度 返回up down left right
utils.getDirectionFromAngle = function(agl) {
var directions = {
up: agl < -45 && agl > -135,
down: agl >= 45 && agl < 135,
left: agl >= 135 || agl <= -135,
right: agl >= -45 && agl <= 45
};
for (var key in directions) {
if (directions[key]) return key;
}
return null;
};
utils.reset = function() {
startEvent = moveEvent = endEvent = null;
__tapped = __touchStart = startSwiping = false;
pos = {start: null,move: null,end: null};
}; //ua
utils.env = (function() {
var os = {}, ua = navigator.userAgent,
android = ua.match(/(Android)[\s\/]+([\d\.]+)/),
ios = ua.match(/(iPad|iPhone|iPod)\s+OS\s([\d_\.]+)/),
wp = ua.match(/(Windows\s+Phone)\s([\d\.]+)/),
isWebkit = /WebKit\/[\d.]+/i.test(ua),
isSafari = ios ? (navigator.standalone ? isWebkit : (/Safari/i.test(ua) && !/CriOS/i.test(ua) && !/MQQBrowser/i.test(ua))) : false;
if (android) {
os.android = true;
os.version = android[2];
}
if (ios) {
os.ios = true;
os.version = ios[2].replace(/_/g, '.');
os.ios7 = /^7/.test(os.version);
if (ios[1] === 'iPad') {
os.ipad = true;
} else if (ios[1] === 'iPhone') {
os.iphone = true;
os.iphone5 = screen.height == 568;
} else if (ios[1] === 'iPod') {
os.ipod = true;
}
}
if (isWebkit) {
os.webkit = true;
}
if (isSafari) {
os.safari = true;
}
return os;
})(); //已配置 tap hold swipe表示是否开启手势
//tapTime tap事件延迟触发的时间
//holdTime hold事件多少秒后触发
//tapMaxDistance 触发tap的时候 最小的移动范围
//swipeMinDistance 触发swipe的时候 最小的移动范围
//swipeTime touchstart 到touchend之前的时间 如果小于swipeTime 才会触发swipe手势
var config = {
tap: true,
tapMaxDistance: 10,
hold: true,
tapTime: 200,
holdTime: 650,
swipe: true,
swipeTime: 300,
swipeMinDistance: 18
};
var smrEventList = {
TOUCH_START: 'touchstart',
TOUCH_MOVE: 'touchmove',
TOUCH_END: 'touchend',
TOUCH_CANCEL: 'touchcancel',
MOUSE_DOWN: 'mousedown',
MOUSE_MOVE: 'mousemove',
MOUSE_UP: 'mouseup',
CLICK: 'click',
PINCH_START: 'pinchstart',
PINCH_END: 'pinchend',
PINCH: 'pinch',
PINCH_IN: 'pinchin',
PINCH_OUT: 'pinchout',
ROTATION_LEFT: 'rotateleft',
ROTATION_RIGHT: 'rotateright',
ROTATION: 'rotate',
SWIPE_START: 'swipestart',
SWIPING: 'swiping',
SWIPE_END: 'swipeend',
SWIPE_LEFT: 'swipeleft',
SWIPE_RIGHT: 'swiperight',
SWIPE_UP: 'swipeup',
SWIPE_DOWN: 'swipedown',
SWIPE: 'swipe',
DRAG: 'drag',
DRAGSTART: 'dragstart',
DRAGEND: 'dragend',
HOLD: 'hold',
TAP: 'tap',
DOUBLE_TAP: 'doubletap'
};
/** 手势识别 */
//记录 开始 移动 结束时候的位置
var pos = {
start: null,
move: null,
end: null
};
var __touchStart = false;
var __tapped;
var __prev_tapped_end_time;
var __prev_tapped_pos;
var __holdTimer = null;
var startTime=0;
var startEvent;
var moveEvent;
var endEvent;
var startSwiping; var gestures = {
swipe: function(ev) {
var el = ev.target; if (!__touchStart || !pos.move || utils.getFingers(ev) > 1) {
return;
} //计算 时间 距离 角度
var now = Date.now();
var touchTime = now - startTime;
var distance = utils.getDistance(pos.start[0], pos.move[0]);
var angle = utils.getAngle(pos.start[0], pos.move[0]);
var direction = utils.getDirectionFromAngle(angle);
var touchSecond = touchTime / 1000;
var eventObj = {
type: smrEventList.SWIPE,
originEvent: ev,
direction: direction,
distance: distance,
distanceX: pos.move[0].x - pos.start[0].x,
distanceY: pos.move[0].y - pos.start[0].y,
x: pos.move[0].x - pos.start[0].x,
y: pos.move[0].y - pos.start[0].y,
angle: angle,
duration: touchTime,
fingersCount: utils.getFingers(ev)
};
if (config.swipe) {
var swipeTo = function() {
var elt = smrEventList;
switch (direction) {
case 'up':
engine.trigger(el, elt.SWIPE_UP, eventObj);
break;
case 'down':
engine.trigger(el, elt.SWIPE_DOWN, eventObj);
break;
case 'left':
engine.trigger(el, elt.SWIPE_LEFT, eventObj);
break;
case 'right':
engine.trigger(el, elt.SWIPE_RIGHT, eventObj);
break;
}
};
if (!startSwiping) {
eventObj.fingerStatus = eventObj.swipe = 'start';
//大于tap的最小距离 才算进入swipe手势
if(distance>config.tapMaxDistance){
startSwiping = true;
}
} else if (utils.isTouchMove(ev)) {
eventObj.fingerStatus = eventObj.swipe = 'move';
engine.trigger(el, smrEventList.SWIPING, eventObj);
} else if (utils.isTouchEnd(ev)|| ev.type === 'mouseout') { eventObj.fingerStatus = eventObj.swipe = 'end';
//事件要短 距离要有点远
if (config.swipeTime > touchTime && distance > config.swipeMinDistance) { swipeTo();
engine.trigger(el, smrEventList.SWIPE, eventObj, false);
}
}
}
},
tap : function(ev){
var el = ev.target;
//如果设置了tap为true 才会触发该手势
if (config.tap) {
var now = Date.now();
var touchTime = now - startTime;
var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
clearTimeout(__holdTimer);
//如果移动的距离比设置的距离大(10) 就不算是tap
if (config.tapMaxDistance < distance) return; __tapped = true;
__prev_tapped_end_time = now;
__prev_tapped_pos = pos.start[0];
__tapTimer = setTimeout(function() {
engine.trigger(el, smrEventList.TAP, {
type: smrEventList.TAP,
originEvent: ev
});
},
config.tapTime);
}
},
hold: function(ev) {
var el = ev.target;
//如果设置了hold为true 才会触发该手势
if (config.hold) {
clearTimeout(__holdTimer);
__holdTimer = setTimeout(function() {
if (!pos.start) return;
var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
//如果移动的距离大于配置的距离(10) 就不触发hold
if (config.tapMaxDistance < distance) return; if (!__tapped) {
engine.trigger(el, "hold", {
type: 'hold',
originEvent: ev,
fingersCount: utils.getFingers(ev),
position: pos.start[0]
});
}
},
config.holdTime);
}
}
} /** 底层事件绑定/代理支持 */
var engine = {
proxyid: 0,
proxies: [],
trigger : function(el, evt, detail){
detail = detail || {};
var e, opt = {
bubbles: true,
cancelable: true,
detail: detail
};
try {
//这里是触发 自定义事件
if (typeof CustomEvent !== 'undefined') {
e = new CustomEvent(evt, opt);
if (el) {
el.dispatchEvent(e);
}
} else {
e = document.createEvent("CustomEvent");
e.initCustomEvent(evt, true, true, detail);
if (el) {
el.dispatchEvent(e);
}
}
} catch (ex) {
console.warn("Touch.js is not supported by environment.");
}
},
bind: function(el, evt, handler) {
el.listeners = el.listeners || {};
//proxy才是真正元素绑定的事件
var proxy = function(e) {
//对ios7的一个兼容 也不知道是什么原理 if (utils.env.ios7) {
utils.forceReflow();
} e.originEvent = e;
for (var p in e.detail) {
if (p !== 'type') {
e[p] = e.detail[p];
}
}
var returnValue = handler.call(e.target, e);
if (typeof returnValue !== "undefined" && !returnValue) {
e.stopPropagation();
e.preventDefault();
}
}; if (!el.listeners[evt]) {
el.listeners[evt] = [proxy];
} else {
el.listeners[evt].push(proxy);
} handler.proxy = handler.proxy || {};
if (!handler.proxy[evt]) {
handler.proxy[evt] = [this.proxyid++];
} else {
handler.proxy[evt].push(this.proxyid++);
}
this.proxies.push(proxy);
if (el.addEventListener) {
el.addEventListener(evt, proxy, false);
}
},
unbind : function(el, evt){
var handlers = el.listeners[evt];
if (handlers && handlers.length) {
handlers.forEach(function(handler) {
el.removeEventListener(evt, handler, false);
});
}
}
} var _on = function(el,evt,handler) {
//绑定事件 支持多元素 多事件绑定噢
var evts = evt.split(" ");
var els = utils.getType(el) === 'string' ? document.querySelectorAll(el) : [el]; evts.forEach(function(evt) {
if (!utils.hasTouch) {
evt = utils.getPCevts(evt);
}
for(var i=0,len=els.length;i<len;i++){
engine.bind(els[i], evt, handler);
}
});
}; var _off = function(els,evts,handler) {
//删除绑定事件 支持多元素 多事件删除绑定噢
var els = utils.getType(els) === 'string' ? document.querySelectorAll(els) : els;
els = els.length ? Array.prototype.slice.call(els) : [els];
els.forEach(function(el) {
evts = evts.split(" ");
evts.forEach(function(evt) {
if (!utils.hasTouch) {
evt = utils.getPCevts(evt);
}
engine.unbind(el, evt, handler);
});
});
return;
}; //这个函数很重要
// doucment的触屏事件全部在这个里面
var handlerOriginEvent = function(ev) {
var el = ev.target; switch (ev.type) {
case 'mousedown':
case 'touchstart':
//记录下刚开始点击的事件和位置
__touchStart = true;
if (!pos.start || pos.start.length < 2) {
pos.start = utils.getPosOfEvent(ev);
}
startTime = Date.now();
startEvent = ev;
gestures.hold(ev);
break;
case 'touchmove':
case 'mousemove':
if (!__touchStart || !pos.start) return;
//记录滑动过程中的位置
pos.move = utils.getPosOfEvent(ev);
gestures.swipe(ev);
break;
case 'touchend':
case 'touchcancel':
case 'mouseup':
case 'moudeout':
if (!__touchStart) return;
endEvent = ev;
//.......
if (startSwiping) {
gestures.swipe(ev);
} else {
gestures.tap(ev);
} utils.reset();
if (ev.touches && ev.touches.length === 1) {
__touchStart = false;
}
break;
}
} var init = function(){
//给 document 绑定 下面这些事件
var mouseEvents = 'mouseup mousedown mousemove',
touchEvents = 'touchstart touchmove touchend touchcancel';
var bindingEvents = utils.hasTouch ? touchEvents : mouseEvents; bindingEvents.split(" ").forEach(function(evt) {
document.addEventListener(evt, handlerOriginEvent, false);
});
}
init();
window.touch = {
on : _on,
off : _off
};
})(); touch.on("#vv","tap",function(){
ss1.innerHTML = ~~ss1.innerHTML+1;
}); touch.on("#vv","swipeleft",function(){
ss2.innerHTML = ~~ss2.innerHTML+1;
});
touch.on("#vv","swiperight",function(){
ss2.innerHTML = ~~ss2.innerHTML-1;
}); touch.on("#vv","hold",function(){
ss.innerHTML = ~~ss.innerHTML+1;
});
</script>
</body>
</html>

zepto的手势源码解析

zepto自己以移动端的jq自喻,然后提供了一套移动端的手势

touch的下载地址 https://github.com/madrobby/zepto/blob/master/src/touch.js#files

这个touch的好处就是可以支持jq的事件绑定方式,比如$("#xx").bind("tap",fun),容易理解,容易上手

这个touch的实现方式和百度的touch实现基本是一样的,document去绑定touchstart,touchmove,touchend,touchcancel然后经过一些列的判断

去掉ms的兼容 , 去掉一些手势后的代码    全部代码下载地址    https://github.com/madrobby/zepto/blob/master/src/touch.js#files

<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>wo ca!~</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
</head>
<style>
.xx{width: 200px;background: #ccc; height: 100px;}
</style>
<body>
<div id="vv" class="xx a"></div>
<br>
<div id="ss" class="xx a">1</div>
<br>
<div id="ss1" class="xx">1</div>
<br>
<div id="ss2" class="xx">1</div>
<br> <script src="http://static.paipaiimg.com/paipai_h5/js/ttj/zepto.min.js"></script>
<script >
// Zepto.js
// (c) 2010-2015 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license. ;(function($){
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture function swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >=
Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
} function longTap() {
longTapTimeout = null
if (touch.last) {
touch.el.trigger('longTap')
touch = {}
}
} function cancelLongTap() {
if (longTapTimeout) clearTimeout(longTapTimeout)
longTapTimeout = null
} function cancelAll() {
if (touchTimeout) clearTimeout(touchTimeout)
if (tapTimeout) clearTimeout(tapTimeout)
if (swipeTimeout) clearTimeout(swipeTimeout)
if (longTapTimeout) clearTimeout(longTapTimeout)
touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
touch = {}
} function isPrimaryTouch(event){
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH)
&& event.isPrimary
} $(document).ready(function(){
var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType $(document)
.on('touchstart', function(e){
//取第一个手指的信息
firstTouch = e.touches[0]
if (e.touches && e.touches.length === 1 && touch.x2) {
// Clear out touch movement data if we have it sticking around
// This can occur if touchcancel doesn't fire due to preventDefault, etc.
touch.x2 = undefined
touch.y2 = undefined
}
//记录点下的时候
now = Date.now()
delta = now - (touch.last || now)
touch.el = $('tagName' in firstTouch.target ?
firstTouch.target : firstTouch.target.parentNode)
touchTimeout && clearTimeout(touchTimeout)
//记录点下的位置
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
touch.last = now
longTapTimeout = setTimeout(longTap, longTapDelay)
})
.on('touchmove', function(e){
firstTouch = e.touches[0];
//记录移动到的位置 和移动的距离
cancelLongTap();
touch.x2 = firstTouch.pageX
touch.y2 = firstTouch.pageY deltaX += Math.abs(touch.x1 - touch.x2)
deltaY += Math.abs(touch.y1 - touch.y2)
})
.on('touchend', function(e){
cancelLongTap()
//判断移动的范围来判断是 tap 还是 swipe
// swipe
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) swipeTimeout = setTimeout(function() {
touch.el.trigger('swipe')
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
touch = {}
}, 0) // normal tap
else if ('last' in touch)
// don't fire tap when delta position changed by more than 30 pixels,
// for instance when moving to a point and back to origin
if (deltaX < 30 && deltaY < 30) {
// delay by one tick so we can cancel the 'tap' event if 'scroll' fires
// ('tap' fires before 'scroll')
tapTimeout = setTimeout(function() {
// trigger universal 'tap' with the option to cancelTouch()
// (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
var event = $.Event('tap')
event.cancelTouch = cancelAll
touch.el.trigger(event)
}, 0)
} else {
touch = {}
}
deltaX = deltaY = 0 })
// when the browser window loses focus,
// for example when a modal dialog is shown,
// cancel all ongoing events
.on('touchcancel', cancelAll) // scrolling the window indicates intention of the user
// to scroll, not tap or swipe, so cancel all ongoing events
$(window).on('scroll', cancelAll)
}) ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
'tap', 'longTap'].forEach(function(eventName){
$.fn[eventName] = function(callback){
//给元素绑定上面的事件
return this.on(eventName, callback)
}
})
})(Zepto);
</script>
<script>
$("#vv").bind("tap",function(){
ss.innerHTML = ~~ss.innerHTML+1;
});
$("#vv").bind("swipeLeft",function(){
ss1.innerHTML = ~~ss1.innerHTML+1;
});
$("#vv").bind("longTap",function(){
ss2.innerHTML = ~~ss2.innerHTML+1;
});
</script>
</body>
</html>

一些我遇到的手势问题

问题1

在有些android的版本上 touchend不触发

在Android 4.0.x的版本上我遇到过,很蛋疼,比如小米1的最开始的版本就遇到过

如果在touchmove中加上 阻止默认行为 是可以的(e.preventDefault();),但是会带来另为一个严重的问题,就是无法向下滑动,真实无解的问题,好在这个版本已经离我们远去

这个问题的一些讨论

https://code.google.com/p/android/issues/detail?id=19827

http://stackoverflow.com/questions/7691551/touchend-event-in-ios-webkit-not-firing

问题2

透传的问题

透传应该分2中,

一种是上面的div隐藏,触发到下面的元素的click,都用tap就可解决,不要一个tap一个click 这样不好

另外一种是上层的元素隐藏,触发到下面input的聚焦,弹出键盘(最常见的场景,就是弹出个这招层,点关闭遮罩层的时候,下面有一个input)

这个问题都找不到好的解决方案,我在项目中的做法是有一个透明的遮罩层,先关闭遮罩层,在等280ms关闭这个透明的遮罩层

javascript 常用手势 分析的更多相关文章

  1. Javascript常用的设计模式详解

    Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...

  2. javascript常用经典算法实例详解

    javascript常用经典算法实例详解 这篇文章主要介绍了javascript常用算法,结合实例形式较为详细的分析总结了JavaScript中常见的各种排序算法以及堆.栈.链表等数据结构的相关实现与 ...

  3. JavaScript 常用功能总结

    小编吐血整理加上翻译,太辛苦了~求赞! 本文主要总结了JavaScript 常用功能总结,如一些常用的JS 对象,基本数据结构,功能函数等,还有一些常用的设计模式. 目录: 众所周知,JavaScri ...

  4. select元素javascript常用操作 转

    /*------------------------------------------------------ *作者:xieyu @ 2007-08-14 *语言:JavaScript *说明:s ...

  5. 第二篇、JavaScript常用的API

    下面是我整理的一些JavaScript常用的API清单. 目录 元素查找 class操作 节点操作 属性操作 内容操作 css操作 位置大小 事件 DOM加载完毕 绑定上下文 去除空格 Ajax JS ...

  6. JavaScript常用正则表达式与应用(一)

    JavaScript的String类和RegExp对象类都定义了相关方法使用正则表达式进行模式匹配,本文将以连载方式介绍JavaScript常用正则表达式与相关应用,欢迎交流 本节是连载一,首先介绍J ...

  7. javascript常用代码大全

    http://caibaojian.com/288.html    原文链接 jquery选中radio //如果之前有选中的,则把选中radio取消掉 $("#tj_cat .pro_ca ...

  8. Javascript 常用函数【3】

    jquery选中radio //如果之前有选中的,则把选中radio取消掉 $("#tj_cat .pro_category").each(function() { if ($(t ...

  9. javascript常用知识点集

    javascript常用知识点集 目录结构 一.jquery源码中常见知识点 二.javascript中原型链常见的知识点 三.常用的方法集知识点 一.jquery源码中常见的知识点 1.string ...

随机推荐

  1. 动态规划:高维DP

    例子当然是王八棋这道题,这道题以前是写烂了 先来一个大暴力,zlw教的暴力~~ #include<iostream> using namespace std; ,maxm=; ]; int ...

  2. MDK stm32 仿真

    直接选择simulator,仿真时报错 *** error 65: access violation at 0x40021000 : no 'read' permission 修改系统配置,原配置如下 ...

  3. HDU 2105 The Center of Gravity (数学)

    题目链接 Problem Description Everyone know the story that how Newton discovered the Universal Gravitatio ...

  4. autoKeras入门

    测试本地mnist数据集 图片只用500张,450张做train与50张test, 代码如下: # conding:utf-8 import os os.environ[' import numpy ...

  5. javascript写的轮播图

    欢迎指点! 先放上效果图: 鼠标移入界面后: <body onselectstart="return false;" style="-moz-user-select ...

  6. 手工安装kubernetes

    参考的URL是 http://www.cnblogs.com/zhenyuyaodidiao/p/6500830.html 安装kubernets本身比较顺利,只是作dashboard时,老是日文版, ...

  7. 在Debian 9上安装和配置Observium网络监控

    https://blog.csdn.net/csgd2000/article/details/80780697

  8. Centos7源码编译安装tengine1.5.1

    安装依赖包 yum install pcre pcre-devel openssl openssl-devel gcc make zlib-devel wget -y 下载和创建用户 mkdir /t ...

  9. python实用技巧 : Filtering os.walk(转)

    ''' Created on Mar 7, 2010 @author: Diego 需求: 得到某个目录下, 符合过滤条件的文件夹/文件.实现: 将os.walk再次包装. TODO: 不知道本程序的 ...

  10. 非负整数可重集去重&排序+获得可重集的全排列的几种方法

    非负整数可重集O(n)去重并排序 可重集是指元素可重复的集合,对于在一定区间内的正整数集,比如[1,n],我们可以在不不使用任何额外空间(包括不使用O(1)的空间)的情况下,用O(n)的时间复杂度完成 ...