前言
现在一直在做移动端的开发,这次将单页应用的网页内嵌入了app,于是老大反映了一个问题:
app应用点击响应慢!
我开始不以为然,于是拿着网页版的试了试,好像确实有一定延迟,于是开始了研究,最后选择了touch取代鼠标事件
但是,touch事件取代mouse事件,还是有一定问题的,据说网上问题很多,因为两者之间还是有一定差异
而且如果完全使用touch事件,对自动化测试的同事来说,他们的系统根本不支持touch事件,再者我们平时网页开发也不方便
所以,了解鼠标事件与touch事件的区别,探讨鼠标事件与touch事件的兼容也是有必要的,于是我们开始今天的学习吧
PS:这里使用zepto框架,懒得自己搞了......
事件差异
鼠标事件
首先,我们来看看鼠标事件相关吧:
1 var startTime; 2 var log = function (msg) { 3 console.log(new Date().getTime() - startTime); 4 console.log(msg); 5 }; 6 var mouseDown = function () { 7 startTime = new Date().getTime(); 8 log('mouseDown'); 9 }; 10 var mouseClick = function () { 11 log('mouseClick'); 12 }; 13 var mouseUp = function () { 14 log('mouseUp'); 15 }; 16 17 document.addEventListener('mousedown', mouseDown); 18 document.addEventListener('click', mouseClick); 19 document.addEventListener('mouseup', mouseUp);
从这里看到了,鼠标顺序是有mousedown -> click -> mouseup 的顺序,其时间差也出来了
touch事件
然后我们看看touch事件
没有click
touch包含三个事件,touchstart、touchmove、touchend,并没有click事件,所以click事件需要自己模拟,这个我们后面来看看
1 var startTime; 2 var log = function (msg) { 3 console.log(new Date().getTime() - startTime); 4 console.log(msg); 5 }; 6 var touchStart = function () { 7 startTime = new Date().getTime(); 8 log('touchStart'); 9 }; 10 11 var touchEnd = function () { 12 log('touchEnd'); 13 }; 14 15 document.addEventListener('touchstart', touchStart); 16 document.addEventListener('touchend', touchEnd);
在chrome开启touch事件的情况下,可以看到这个结果
混合事件
现在我们在手机上同时触发两者事件看看区别,这里代码做一定修改
测试地址
http://sandbox.runjs.cn/show/ey54cgqf
此处手机与电脑有非常大的区别!!!
结论
不要同时给document绑定鼠标与touch事件
document.addEventListener('mousedown', mouseDown); document.addEventListener('click', mouseClick); document.addEventListener('mouseup', mouseUp); document.addEventListener('touchstart', touchStart); document.addEventListener('touchend', touchEnd);
这个样子,在手机上不会触发click事件,click事件要绑定到具体元素
PS:此处的原因我就不去研究了,如果您知道为什么,请留言
手机上mousedown本来响应就慢
经过测试,电脑上touch与click事件的差距不大,但是手机上,当我们手触碰屏幕时,要过300ms左右才会触发mousedown事件
所以click事件在手机上响应就是慢一拍
数据说明
可以看到,在手机上使用click事件其实对用户体验并不好,所以我们可能会逐步使用touch事件
参数差异
现在,我们来看看鼠标与touch事件的参数差异
事件参数(touchstart/mouseup)
我们来看几个关键的地方:
changedTouches/touches/targetTouches
touches:为屏幕上所有手指的信息
PS:因为手机屏幕支持多点触屏,所以这里的参数就与手机有所不同
targetTouches:手指在目标区域的手指信息
changedTouches:最近一次触发该事件的手指信息
比如两个手指同时触发事件,2个手指都在区域内,则容量为2,如果是先后离开的的话,就会先触发一次再触发一次,这里的length就是1,只统计最新的
PS:一般changedTouches的length都是1
touchend时,touches与targetTouches信息会被删除,changedTouches保存的最后一次的信息,最好用于计算手指信息
这里要使用哪个数据各位自己看着办吧,我也不是十分清晰(我这里还是使用changedTouches吧)
参数信息(changedTouches[0])
几个重要通用点:
① clientX:在显示区的坐标
② pageX:鼠标在页面上的位置
③ screenX:鼠标在显示屏上的坐标(我是双屏所以x很大)
④ target:当前元素
几个重要不同点:
① layerX:这个是相对距离,这个不同,所以不要用这个东西了
② ......
这个有必要说明下,比如我们改下代码:
测试地址
http://sandbox.runjs.cn/show/7tyo48bf
各位自己运行看看差异吧
简单扩展touch事件
touch没有click事件,于是有zepto搞了个tap事件,我们这里先来简单模拟一下,再看源码怎么干的
1 var mouseData = { 2 sTime: 0, 3 eTime: 0, 4 sX: 0, 5 eX: 0, 6 sY: 0, 7 eY: 0 8 }; 9 var log = function (msg) { 10 console.log(msg); 11 }; 12 var touchStart = function (e) { 13 var pos = e.changedTouches[0]; 14 mouseData.sTime = new Date().getTime(); 15 mouseData.sX = pos.pageX; 16 mouseData.sY = pos.pageY; 17 }; 18 var touchMove = function (e) { 19 // var pos = e.changedTouches[0]; 20 // mouseData.eTime = new Date().getTime(); 21 // mouseData.eX = pos.pageX; 22 // mouseData.eY = pos.pageY; 23 e.preventDefault(); 24 return false; 25 }; 26 var touchEnd = function (e) { 27 var pos = e.changedTouches[0]; 28 mouseData.eTime = new Date().getTime(); 29 mouseData.eX = pos.pageX; 30 mouseData.eY = pos.pageY; 31 var data = onTouchEnd(); 32 log(data); 33 var d = $('body'); 34 d.append($('<div>间隔:' + data.timeLag + ', 方向:' + data.dir + '</div>')); 35 }; 36 var onTouchEnd = function () { 37 //时间间隔 38 var timeLag = mouseData.eTime - mouseData.sTime; 39 //移动状态,默认乱移动 40 var dir = 'move'; 41 if (mouseData.sX == mouseData.eX) { 42 if (mouseData.eY - mouseData.sY > 0) dir = 'down'; 43 if (mouseData.eY - mouseData.sY < 0) dir = 'up'; 44 if (mouseData.eY - mouseData.sY == 0) dir = 'tap'; 45 } 46 if (mouseData.sY == mouseData.eY) { 47 if (mouseData.eX - mouseData.sX > 0) dir = 'right'; 48 if (mouseData.eX - mouseData.sX < 0) dir = 'left'; 49 if (mouseData.eX - mouseData.sX == 0) dir = 'tap'; 50 } 51 return { 52 timeLag: timeLag, 53 dir: dir 54 }; 55 }; 56 57 var touchEvents = function (el, func) { 58 el = el || document; 59 func = func || function () { }; 60 el.addEventListener('touchstart', touchStart); 61 el.addEventListener('touchmove', touchMove); 62 el.addEventListener('touchend', touchEnd); 63 }; 64 var d = $('body'); 65 touchEvents(d[0]);
测试地址
http://sandbox.runjs.cn/show/2n9nqssv
这里就可以看到一次touch事件是tap还是up等属性,当然很多时候我们需要设置x方向或者y方向不可拖动,这样就更好呈现
时间间隔长短可以让我们判断自己的拖动是长拖动还是短拖动,长拖动也许用户希望动画慢点,短拖动也许动画就快了
touch事件代码汇总
1 var log = function (msg) { 2 console.log(msg); 3 }; 4 var d = $('body'); 5 6 var touchEvents = function (el, type, func) { 7 this.long = 400; //用于设置长点击阀值 8 this.el = el || document; 9 this.func = func || function () { }; 10 this.type = type || 'tap'; 11 this.mouseData = { 12 sTime: 0, 13 eTime: 0, 14 sX: 0, 15 eX: 0, 16 sY: 0, 17 eY: 0 18 }; 19 this.addEvent(); 20 21 }; 22 touchEvents.prototype = { 23 constructor: touchEvents, 24 addEvent: function () { 25 var scope = this; 26 this.startFn = function (e) { 27 scope.touchStart.call(scope, e); 28 }; 29 this.moveFn = function (e) { 30 scope.touchMove.call(scope, e); 31 }; 32 this.endFn = function (e) { 33 scope.touchEnd.call(scope, e); 34 }; 35 this.el.addEventListener('touchstart', this.startFn); 36 //此处可以换成这样 37 // document.addEventListener('touchmove', this.touchMove); 38 this.el.addEventListener('touchmove', this.moveFn); 39 this.el.addEventListener('touchend', this.endFn); 40 }, 41 removeEvent: function () { 42 this.el.removeEventListener('touchstart', this.touchStart); 43 this.el.removeEventListener('touchmove', this.touchMove); 44 this.el.removeEventListener('touchend', this.touchEnd); 45 }, 46 touchStart: function (e) { 47 var pos = e.changedTouches[0]; 48 this.mouseData.sTime = new Date().getTime(); 49 this.mouseData.sX = pos.pageX; 50 this.mouseData.sY = pos.pageY; 51 }, 52 touchMove: function (e) { 53 e.preventDefault(); 54 return false; 55 }, 56 touchEnd: function (e) { 57 var pos = e.changedTouches[0]; 58 this.mouseData.eTime = new Date().getTime(); 59 this.mouseData.eX = pos.pageX; 60 this.mouseData.eY = pos.pageY; 61 this.onTouchEnd(); 62 }, 63 onTouchEnd: function () { 64 if (this.type == this._getDir()) { 65 66 } 67 }, 68 _getDir: function () { 69 //时间间隔,间隔小于100都认为是快速,大于400的认为是慢速 70 var timeLag = this.mouseData.eTime - this.mouseData.sTime; 71 var dir = 'swipe'; 72 if (timeLag > this.long) dir = 'longSwipe'; 73 if (this.mouseData.sX == this.mouseData.eX && this.mouseData.sY == this.mouseData.eY) { 74 dir = 'tap'; 75 if (timeLag > this.long) dir = 'longTap'; 76 } else { 77 if (Math.abs(this.mouseData.eY - this.mouseData.sY) > Math.abs(this.mouseData.eX - this.mouseData.sX)) { 78 dir = this._getUDDir(dir); 79 } else { 80 dir = 'swipe'; 81 dir = this._getLRDir(dir); 82 } 83 } 84 log(dir); 85 d.append($('<div>间隔:' + timeLag + ', 方向:' + dir + '</div>')); 86 return dir; 87 }, 88 //单独用于计算上下的 89 _getUDDir: function (dir) { 90 if (this.mouseData.eY - this.mouseData.sY > 0) dir += 'Down'; 91 if (this.mouseData.eY - this.mouseData.sY < 0) dir += 'Up'; 92 return dir; 93 }, 94 //计算左右 95 _getLRDir: function (dir) { 96 if (this.mouseData.eX - this.mouseData.sX > 0) dir += 'Right'; 97 if (this.mouseData.eX - this.mouseData.sX < 0) dir += 'Left'; 98 return dir; 99 } 100 }; 101 102 new touchEvents(d[0], 'swipe', function () { 103 // d.append($('<div>间隔:' + data.timeLag + ', 方向:' + data.dir + '</div>')); 104 });
测试地址
http://sandbox.runjs.cn/show/rpohk79w
测试时请使用chrome,并且开启touch事件
测试效果
完整可绑定事件代码
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <title></title> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 <script id="others_zepto_10rc1" type="text/javascript" class="library" src="http://sandbox.runjs.cn/js/sandbox/other/zepto.min.js"></script> 7 </head> 8 <body> 9 <div id="d" style="position: absolute; top: 50px; left: 50px; 100px; height: 100px; 10 border: 1px solid black;">滑动我 11 </div> 12 </body> 13 <script type="text/javascript"> 14 var log = function (msg) { 15 console.log(msg); 16 }; 17 var d = $('body'); 18 19 var touchEvents = function (el, type, func) { 20 this.long = 400; //用于设置长点击阀值 21 this.el = el || document; 22 this.func = func || function () { }; 23 this.type = type || 'tap'; 24 this.mouseData = { 25 sTime: 0, 26 eTime: 0, 27 sX: 0, 28 eX: 0, 29 sY: 0, 30 eY: 0 31 }; 32 this.addEvent(); 33 34 }; 35 touchEvents.prototype = { 36 constructor: touchEvents, 37 addEvent: function () { 38 var scope = this; 39 this.startFn = function (e) { 40 scope.touchStart.call(scope, e); 41 }; 42 this.moveFn = function (e) { 43 scope.touchMove.call(scope, e); 44 }; 45 this.endFn = function (e) { 46 scope.touchEnd.call(scope, e); 47 }; 48 this.el.addEventListener('touchstart', this.startFn); 49 //此处可以换成这样 50 // document.addEventListener('touchmove', this.touchMove); 51 this.el.addEventListener('touchmove', this.moveFn); 52 this.el.addEventListener('touchend', this.endFn); 53 }, 54 removeEvent: function () { 55 this.el.removeEventListener('touchstart', this.touchStart); 56 this.el.removeEventListener('touchmove', this.touchMove); 57 this.el.removeEventListener('touchend', this.touchEnd); 58 }, 59 touchStart: function (e) { 60 var pos = e.changedTouches[0]; 61 this.mouseData.sTime = new Date().getTime(); 62 this.mouseData.sX = pos.pageX; 63 this.mouseData.sY = pos.pageY; 64 }, 65 touchMove: function (e) { 66 e.preventDefault(); 67 return false; 68 }, 69 touchEnd: function (e) { 70 var pos = e.changedTouches[0]; 71 this.mouseData.eTime = new Date().getTime(); 72 this.mouseData.eX = pos.pageX; 73 this.mouseData.eY = pos.pageY; 74 this.onTouchEnd(e); 75 }, 76 onTouchEnd: function (e) { 77 if (this.type == this._getDir()) { 78 this.func(e, this); 79 } 80 }, 81 _getDir: function () { 82 //时间间隔,间隔小于100都认为是快速,大于400的认为是慢速 83 var timeLag = this.mouseData.eTime - this.mouseData.sTime; 84 var dir = 'swipe'; 85 if (timeLag > this.long) dir = 'longSwipe'; 86 if (this.mouseData.sX == this.mouseData.eX && this.mouseData.sY == this.mouseData.eY) { 87 dir = 'tap'; 88 if (timeLag > this.long) dir = 'longTap'; 89 } else { 90 if (Math.abs(this.mouseData.eY - this.mouseData.sY) > Math.abs(this.mouseData.eX - this.mouseData.sX)) { 91 dir = this._getUDDir(dir); 92 } else { 93 dir = this._getLRDir(dir); 94 } 95 } 96 log(dir); 97 d.append($('<div>间隔:' + timeLag + ', 方向:' + dir + '</div>')); 98 return dir; 99 }, 100 //单独用于计算上下的 101 _getUDDir: function (dir) { 102 if (this.mouseData.eY - this.mouseData.sY > 0) dir += 'Down'; 103 if (this.mouseData.eY - this.mouseData.sY < 0) dir += 'Up'; 104 return dir; 105 }, 106 //计算左右 107 _getLRDir: function (dir) { 108 if (this.mouseData.eX - this.mouseData.sX > 0) dir += 'Right'; 109 if (this.mouseData.eX - this.mouseData.sX < 0) dir += 'Left'; 110 return dir; 111 } 112 }; 113 114 new touchEvents(d[0], 'tap', function (e) { 115 log(arguments); 116 }); 117 118 </script> 119 </html>
这个代码基本可用了,但是使用上不是很方便,我们这里就不关注了,下面我们来看看zepto的代码和兼容问题
zepto的touch与兼容
先上zepto源码,一看就知道我写的有多不行啦!
touch对象与上面mouseData功效相同,记录一些属性
delta 用于记录两次点击的间隔,间隔短就是双击
swipeDirection 函数与_getDir _getUDDir _getLRDir 功能相似,只不过代码更为简练,并且真正的私有化了
63行代码开始,若是代码移动过便是划屏,否则就是点击,这点我也没考虑到
73行,否则就应该是点击,这里并且判断是否存在结束时间,代码比较健壮,做了双击或者快速点击的判断
开始兼容
zepto代码我自然没有资格去评说,现在我们来看看他的兼容问题
PS:我这里很水,不太敢动源码,就加一个tap判断,因为也只是用了这个,具体大动手脚的事情,我们后面再做
这样做事因为,我们的项目主要是把click改成了tap事件,导致页面很多功能不可用
1 ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function (m) { 2 //兼容性方案处理,以及后期资源清理,如果为假时候,就触发点击事件 3 var isTouch = 'ontouchstart' in document.documentElement; 4 if(m === 'tap' && isTouch === false) { 5 $.fn[m] = function (callback) { return this.bind('click', callback) } 6 } else { 7 $.fn[m] = function (callback) { return this.bind(m, callback) } 8 } 9 })
我就干了这么一点点事情......