移动web风风火火几多年,让我这个在Pc端漂流的前端er不免心生仰慕,的确入行几多年,也该是时候进军移动web了。移动web中踩到的第一个坑就是事件问题,所以在吸取众大神的经验后,特作总结以示后来者。
移动端事件的变化
首先PC端那一堆非常happy的鼠标事件没了,mousedown
, mouseup
, mousemove
, mouseover
, mouseout
, mouseenter
, mouseleave
全都没了,click
也与之前有所差别。取而代之的是几个原始的事件。
-touchstart
-touchmove
-touchend
-touchcancel
同样事件处理函数中的event
也与pc端有着极大的差别,最典型的是增加了三个与触摸相关的属性:
-touches
-changedTouches
-targetTouches
在pc端一台机器只会有一个鼠标,所以与鼠标相关的属性都可以放到一个event对象上,但是移动端设备大多支持多点触控,这就意味着一个事件可能与多个触控点相关,每个触控点都需要记录自己单独的属性。所以event对象中与touch相关的三个属性都是TouchList
类型,与触控位置、目标元素、全都放到了touch对象上。
Touch对象主要属性如下:
-clientX / clientY:触摸点相对浏览器窗口的位置
-pageX / pageY:触摸点相对于页面的位置
-screenX / screenY:触摸点相对于屏幕的位置
-identifier:touch对象的ID
-target:当前的DOM元素
现在反过来看看几个touch相关事件,并与pc端事件做一下对比:
–touchstart
: 触控最开始时发生,类似于pc端的mousedown事件
–touchmove
: 触控点在屏幕上移动时触发,类似于mousemove。但是在当在移动设备上,触控点从一个元素移动到另一个元素上时,并不会像pc端一样触发类似mouseover
/mouseout
mouseenter
/mouseleave
的事件。
–touchend
: 在触摸结束时触发,类似mouseup
–touchcancel
: 当一些更高级别的事情发生时,浏览器会触发该事件。比如突然来了一个电话,这时候会触发touchcanel事件。如果是在游戏中,就要在touchcancel时保存当前游戏的状态信息。
–click
: 移动端的click事件虽然存在,但与pc端有着明显的差异。这也就是著名的300ms问题,以及为了解决300ms延迟带来的点透问题。
这几个事件的事件对象的target属性永远是触控事件最先发生的那个元素
移动端事件的规范化
先把click的问题放一下,我们先考虑以下能否在移动端模拟pc事件呢?答案是可以的。首先我们需要定义一下标准事件:
press -> mousedown
release -> mouseup
move -> mousemove
cancel -> mouseleave
over -> mouseover
out -> mouseout
enter -> mouseenter
leave -> mouseleave
总体看来如下图所示:
在我们定义好标准时候就要考虑如何去实现,值得庆幸的是,事件的传播阶段并没有变化,这里要感谢微软不来添乱。盗一张图:
我们先来看toucmove
,单看名字容易让人想当然的认为它与mousemove对应,然后上文说过了,当触控点在不同元素上移动时,并不会触发mouseover
/mouseout
mouseenter
/mouseleave
等事件,为了实现上面所说的over
, out
, enter
, leave
我们首先要能够在touchmove
中拿到当前位置的dom元素。
浏览器为我们提供了elementFromPoint
方法,这个函数根据clientX
/clientY
来选中最上层的dom元素,这为我们在touchmove中实时获取最近的dom元素提供了保障。当触控点从一个元素移动到另一个元素上时,对移出元素触发mytouchout
事件对移入元素触发mytouchover
事件,同时对与触摸元素当触控点在其上移动时触发mytouchmove
事件。
关于自定义事件,当然是使用createEvent, initEvent, dispatchEvent三个函数,这三个函数并不是本文重点,请大家自行查阅《JavaScript高级程序设计第三版》13章中关于自定义事件的内容。
如此一来,我们的move、over、out等事件就有了着落,而press也非常简单,只需要绑定touchstart即可,同样cancel也只需要绑定touchcancel即可。
对于release我们不能简单的绑定touchend。因为上文已经说过,touchend中touch的target属性对应的是最初触控的元素,并不会随着触控点位置而改变。即是最终在元素B上拿开手指,touchend仍然会发生在元素A上。所以我们需要在touchend时,利用elementFromPoint
获取最后触摸元素,在它身上触发mytouchend
事件来模拟release。
根据事件传播的三个阶段,最适合做这些事的阶段应位于冒泡阶段,代码如下:
首先定义事件绑定与发射函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function on(node, type, listener) { node.addEventListener(type, listener); return { remove: function() { node.removeEventListener(type, listener); } }; } function emit(node, type, evt) { var ne = document.createEvent('HTMLEvents'); ne.initEvent(type, !!evt.bubbles, !!evt.canCancel); for (var p in evt) { if (!(p in ne)) { ne[p] = evt[p]; } } //The return value is false if at least one of the event handlers //which handled this event called Event.preventDefault(). Otherwise it returns true. // 如果注册的回调事件中有的调用了preventDefault方法,dispatEvent返回false,否则都返回true return node.dispatchEvent(ne); } function elementFromPoint(evt) { var touch = evt.changedTouches[0]; return doc.elementFromPoint(touch.clientX, touch.clientY); } |
然后模拟mouse事件,分别在document上添加touchstart
, touchmove
, touchend
的事件处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
doc.addEventListener('DOMContentLoaded', function() { var hoverNode = document.body; doc.addEventListener('touchstart', function(evt) { lastTouchTime = Date.now(); var newNode = evt.target; if (hoverNode) { emit(hoverNode, 'mytouchout', { relatedTarget: newNode, bubbles: true }); } emit(newNode, 'mytouchover', { relatedTarget: hoverNode, bubbles: true }); ڋ件的变化">移动端事件的变化
首先PC端那一堆非常happy的鼠标事件没了, -touchstart 同样事件处理函数中的 -touches 在pc端一台机器只会有一个鼠标,所以与鼠标相关的属性都可以放到一个event对象上,但是移动端设备大多支持多点触控,这就意味着一个事件可能与多个触控点相关,每个触控点都需要记录自己单独的属性。所以event对象中与touch相关的三个属性都是 Touch对象主要属性如下: 现在反过来看看几个touch相关事件,并与pc端事件做一下对比: 移动端事件的规范化先把click的问题放一下,我们先考虑以下能否在移动端模拟pc事件呢?答案是可以的。首先我们需要定义一下标准事件:
总体看来如下图所示: 在我们定义好标准时候就要考虑如何去实现,值得庆幸的是,事件的传播阶段并没有变化,这里要感谢微软不来添乱。盗一张图: 我们先来看 关于自定义事件,当然是使用createEvent, initEvent, dispatchEvent三个函数,这三个函数并不是本文重点,请大家自行查阅《JavaScript高级程序设计第三版》13章中关于自定义事件的内容。 如此一来,我们的move、over、out等事件就有了着落,而press也非常简单,只需要绑定touchstart即可,同样cancel也只需要绑定touchcancel即可。
然后模拟mouse事件,分别在document上添加
|