javascript 常用手势 分析
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/
一些要准备的基础
1.对touch相关的东西要了解
指尖上的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 常用手势 分析的更多相关文章
- Javascript常用的设计模式详解
Javascript常用的设计模式详解 阅读目录 一:理解工厂模式 二:理解单体模式 三:理解模块模式 四:理解代理模式 五:理解职责链模式 六:命令模式的理解: 七:模板方法模式 八:理解javas ...
- javascript常用经典算法实例详解
javascript常用经典算法实例详解 这篇文章主要介绍了javascript常用算法,结合实例形式较为详细的分析总结了JavaScript中常见的各种排序算法以及堆.栈.链表等数据结构的相关实现与 ...
- JavaScript 常用功能总结
小编吐血整理加上翻译,太辛苦了~求赞! 本文主要总结了JavaScript 常用功能总结,如一些常用的JS 对象,基本数据结构,功能函数等,还有一些常用的设计模式. 目录: 众所周知,JavaScri ...
- select元素javascript常用操作 转
/*------------------------------------------------------ *作者:xieyu @ 2007-08-14 *语言:JavaScript *说明:s ...
- 第二篇、JavaScript常用的API
下面是我整理的一些JavaScript常用的API清单. 目录 元素查找 class操作 节点操作 属性操作 内容操作 css操作 位置大小 事件 DOM加载完毕 绑定上下文 去除空格 Ajax JS ...
- JavaScript常用正则表达式与应用(一)
JavaScript的String类和RegExp对象类都定义了相关方法使用正则表达式进行模式匹配,本文将以连载方式介绍JavaScript常用正则表达式与相关应用,欢迎交流 本节是连载一,首先介绍J ...
- javascript常用代码大全
http://caibaojian.com/288.html 原文链接 jquery选中radio //如果之前有选中的,则把选中radio取消掉 $("#tj_cat .pro_ca ...
- Javascript 常用函数【3】
jquery选中radio //如果之前有选中的,则把选中radio取消掉 $("#tj_cat .pro_category").each(function() { if ($(t ...
- javascript常用知识点集
javascript常用知识点集 目录结构 一.jquery源码中常见知识点 二.javascript中原型链常见的知识点 三.常用的方法集知识点 一.jquery源码中常见的知识点 1.string ...
随机推荐
- spring结合Mybatis的框架搭建(一)
一:前沿 2015年新年上班的第二天,第一天就打了一天的酱油哦,只是下午开始搭建自己毕业设计的框架,搭建的是spring+spring mvc+MyBatis的框架.今天遇到了一个问题,结果弄了我一天 ...
- 【BZOJ4031】【HEOI2015】小Z的房间 [Matrix-Tree][行列式]
小Z的房间 Time Limit: 10 Sec Memory Limit: 256 MB[Submit][Status][Discuss] Description 你突然有了一个大房子,房子里面有 ...
- 51nod 最大M子段和系列
1052 最大M子段和 N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的.如果M >= N个数中正数的个数,那么输出所 ...
- Extjs3.4 合并单元格
Ext3.4合并单元格 表格上添加grid-row-span样式
- 输入子系统--event层分析【转】
转自:http://blog.csdn.net/beyondioi/article/details/9186723 ########################################## ...
- linux USB HOST之EHCI和OHCI【转】
转自:http://blog.csdn.net/ljzcom/article/details/8186914 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 2 关键数据结 ...
- 【 Linux】脚本导入格式
在从windows文本(*.txt)格式导入到Linux中时,需要注意. 如果是直接将*.txt 导入到Linux系统,然后重命名使用会有问题,建议在linux系统中创建文件,然后直接复制内容到lin ...
- C++类中引用成员和常量成员的初始化(初始化列表)
如果一个类是这样定义的: Class A { public: A(int pram1, int pram2, int pram3); privite: int a; int &b; const ...
- libev 学习使用
libev 简单的I/O库. a high performance full featured event loop written in c libev 的大小也比 libevent 小得多并且自 ...
- 发布message给其他包使用
https://answers.ros.org/question/65716/which-is-the-correct-way-to-install-header-files-in-catkin-pa ...