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关闭这个透明的遮罩层