手持设备点击响应速度,鼠标事件与touch事件的那些事

前言

现在一直在做移动端的开发,这次将单页应用的网页内嵌入了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事件的情况下,可以看到这个结果



混合事件

现在我们在手机上同时触发两者事件看看区别,这里代码做一定修改

 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     <script id="others_zepto_10rc1" type="text/javascript" class="library" src="http://sandbox.runjs.cn/js/sandbox/other/zepto.min.js"></script>
 6 </head>
 7 <body>
 8     <div id="d" style="width: 100px; height: 100px; border: 1px solid black;">
 9     </div>
10 </body>
11 <script type="text/javascript">
12     var startTime;
13     var log = function (msg) {
14         var div = $('<div></div>');
15         div.html((new Date().getTime()) + ': ' + (new Date().getTime() - startTime) + ': ' + msg)
16         $('body').append(div);
17 
18     };
19     var touchStart = function () {
20         startTime = new Date().getTime();
21         log('touchStart');
22     };
23     var touchEnd = function () {
24         log('touchEnd');
25 
26     };
27     var mouseDown = function () {
28         log('mouseDown');
29     };
30     var mouseClick = function () {
31         log('mouseClick');
32     };
33     var mouseUp = function () {
34         log('mouseUp');
35 
36     };
37     var d = $('#d');
38     d.bind('mousedown', mouseDown);
39     d.bind('click', mouseClick);
40     d.bind('mouseup', mouseUp);
41     d.bind('touchstart', touchStart);
42     d.bind('touchend', touchEnd);
43 </script>
44 </html>
测试地址

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事件的参数差异

 1 var startTime;
 2 var log = function (msg, e) {
 3     console.log(e);
 4     var div = $('<div></div>');
 5     div.html((new Date().getTime()) + ': ' + (new Date().getTime() - startTime) + ': ' + msg)
 6     $('body').append(div);
 7 
 8 };
 9 var touchStart = function (e) {
10     startTime = new Date().getTime();
11     log('touchStart', e);
12 };
13 var touchEnd = function (e) {
14     log('touchEnd', e);
15 
16 };
17 var mouseDown = function (e) {
18     log('mouseDown', e);
19 };
20 var mouseClick = function (e) {
21     log('mouseClick', e);
22 };
23 var mouseUp = function (e) {
24     log('mouseUp', e);
25 
26 };
27 var d = $('#d');
28 d.bind('mousedown', mouseDown);
29 d.bind('click', mouseClick);
30 d.bind('mouseup', mouseUp);
31 d.bind('touchstart', touchStart);
32 d.bind('touchend', touchEnd);
事件参数(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:这个是相对距离,这个不同,所以不要用这个东西了

② ......

这个有必要说明下,比如我们改下代码:

 View Code
测试地址

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事件

测试效果

完整可绑定事件代码

 View Code
这个代码基本可用了,但是使用上不是很方便,我们这里就不关注了,下面我们来看看zepto的代码和兼容问题

zepto的touch与兼容

先上zepto源码,一看就知道我写的有多不行啦!

 View Code
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   })
我就干了这么一点点事情......

待续

今天耗时过长,暂时到这里,对鼠标等操作,对event参数的兼容我们后面点再看看


本文转自叶小钗博客园博客,原文链接http://www.cnblogs.com/yexiaochai/p/3377900.html,如需转载请自行联系原作者

上一篇:一道小小面试题的细节分析


下一篇:如何将松散的dll打包进需要发布的exe