读Zepto源码之Touch模块
2017-09-20 00:00
435 查看
大家都知道,因为历史原因,移动端上的点击事件会有
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto
从上面的代码中可以看到,
swipe: 滑动事件
swipeLeft: 向左滑动事件
swipeRight: 向右滑动事件
swipeUp: 向上滑动事件
swipeDown: 向下滑动事件
doubleTap: 屏幕双击事件
tap: 屏幕点击事件,比
singleTap: 屏幕单击事件
longTap: 长按事件
并且为每个事件都注册了快捷方法。
返回的是滑动的方法。
这里有多组三元表达式,首先对比的是
在
在
触发长按事件。
在触发
撤销
如果有触发
3ff0
最后同样需要将
清除所有事件的执行。
其实就是清除所有相关的定时器,最后将
是否为主触点。
当
触发的是否为
在低版本的移动端 IE 浏览器中,只实现了
先来说明几个变量,
从上面可以看到,
创建手势对象
指定目标元素
指定手势识别时需要处理的指针
这段代码包含了前两步。
这段是第三步,用
接下来就是分析手势了,
如果
这里还将
这里的判断其实就是只处理
因为
如果还需要记录,终点坐标是需要更新的。
正常情况下,
这里有一点不太明白,为什么只会在
如果
可以很清楚地看到,
将
同时开始长按事件定时器,从上面的代码可以看到,长按事件会在
要注意这里还调用了
进入
可以看到,起点和终点的距离超过
在触发完
注意,
终于看到重点了,首先判断
如果不是
在最后会将
触发
这里同样用了
这个
因此,可以知道,在触发
如果不是
在接受到
从前面的分析可以看到,所有的事件触发都是异步的。
因为在
读Zepto源码之内部方法
读Zepto源码之工具函数
读Zepto源码之神奇的$
读Zepto源码之集合操作
读Zepto源码之集合元素查找
读Zepto源码之操作DOM
读Zepto源码之样式操作
读Zepto源码之属性操作
读Zepto源码之Event模块
读Zepto源码之IE模块
读Zepto源码
3ff0
之Callbacks模块
读Zepto源码之Deferred模块
读Zepto源码之Ajax模块
读Zepto源码之Assets模块
读Zepto源码之Selector模块
PointerEvent
Pointer events
TouchEvent
Touch
GestureEvent
MSGestureEvent
一步一步DIY zepto库,研究zepto源码8--touch模块
zepto源码学习-06 touch
zepto源码之touch.js
addPointer method
最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
作者:对角另一面
300ms左右的延迟,
Zepto的
touch模块解决的就是移动端点击延迟的问题,同时也提供了滑动的
swipe事件。
读 Zepto 源码系列文章已经放到了github上,欢迎star: reading-zepto
源码版本
本文阅读的源码为 zepto1.2.0GitBook
《reading-zepto》实现的事件
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){ $.fn[eventName] = function(callback){ return this.on(eventName, callback) } })
从上面的代码中可以看到,
Zepto实现了以下的事件:
swipe: 滑动事件
swipeLeft: 向左滑动事件
swipeRight: 向右滑动事件
swipeUp: 向上滑动事件
swipeDown: 向下滑动事件
doubleTap: 屏幕双击事件
tap: 屏幕点击事件,比
click事件响应更快
singleTap: 屏幕单击事件
longTap: 长按事件
并且为每个事件都注册了快捷方法。
内部方法
swipeDirection
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') }
返回的是滑动的方法。
x1为
x轴起点坐标,
x2为
x轴终点坐标,
y1为
y轴起点坐标,
y2为
y轴终点坐标。
这里有多组三元表达式,首先对比的是
x轴和
y轴上的滑动距离,如果
x轴的滑动距离比
y轴大,则为左右滑动,否则为上下滑动。
在
x轴上,如果起点位置比终点位置大,则为向左滑动,返回
Left,否则为向右滑动,返回
Right。
在
y轴上,如果起点位置比终点位置大,则为向上滑动,返回
Up,否则为向下滑动,返回
Down。
longTap
var touch = {}, touchTimeout, tapTimeout, swipeTimeout, longTapTimeout, longTapDelay = 750, gesture function longTap() { longTapTimeout = null if (touch.last) { touch.el.trigger('longTap') touch = {} } }
触发长按事件。
touch对象保存的是触摸过程中的信息。
在触发
longTap事件前,先将保存定时器的变量
longTapTimeout释放,如果
touch对象中存在
last,则触发
longTap事件,
last保存的是最后触摸的时间。最后将
touch重置为空对象,以便下一次使用。
cancelLongTap
function cancelLongTap() { if (longTapTimeout) clearTimeout(longTapTimeout) longTapTimeout = null }
撤销
longTap事件的触发。
如果有触发
longTap的定时器,清除定时器即可阻止
3ff0
longTap事件的触发。
最后同样需要将
longTapTimeout变量置为
null,等待垃圾回收。
cancelAll
function cancelAll() { if (touchTimeout) clearTimeout(touchTimeout) if (tapTimeout) clearTimeout(tapTimeout) if (swipeTimeout) clearTimeout(swipeTimeout) if (longTapTimeout) clearTimeout(longTapTimeout) touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null touch = {} }
清除所有事件的执行。
其实就是清除所有相关的定时器,最后将
touch对象设置为
null。
isPrimaryTouch
function isPrimaryTouch(event){ return (event.pointerType == 'touch' || event.pointerType == event.MSPOINTER_TYPE_TOUCH) && event.isPrimary }
是否为主触点。
当
pointerType为
touch并且
isPrimary为
true时,才为主触点。
pointerType可为
touch、
pen和
mouse,这里只处理手指触摸的情况。
isPointerEventType
function isPointerEventType(e, type){ return (e.type == 'pointer'+type || e.type.toLowerCase() == 'mspointer'+type) }
触发的是否为
pointerEvent。
在低版本的移动端 IE 浏览器中,只实现了
PointerEvent,并没有实现
TouchEvent,所以需要这个来判断。
事件触发
整体分析
$(document).ready(function(){ var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType $(document) .bind('MSGestureEnd', function(e){ ... }) .on('touchstart MSPointerDown pointerdown', function(e){ ... }) .on('touchmove MSPointerMove pointermove', function(e){ ... }) .on('touchend MSPointerUp pointerup', function(e){ ... }) .on('touchcancel MSPointerCancel pointercancel', cancelAll) $(window).on('scroll', cancelAll)
先来说明几个变量,
now用来保存当前时间,
delta用来保存两次触摸之间的时间差,
deltaX用来保存
x轴上的位移,
deltaY来用保存
y轴上的位移,
firstTouch保存初始触摸点的信息,
_isPointerType保存是否为
pointerEvent的判断结果。
从上面可以看到,
Zepto所触发的事件,是从
touch、
pointer或者 IE 的
guesture事件中,根据不同情况计算出来的。这些事件都绑定在
document上。
IE Gesture 事件的处理
IE的手势使用,需要经历三步:
创建手势对象
指定目标元素
指定手势识别时需要处理的指针
if ('MSGesture' in window) { gesture = new MSGesture() gesture.target = document.body }
这段代码包含了前两步。
on('touchstart MSPointerDown pointerdown', function(e){ ... if (gesture && _isPointerType) gesture.addPointer(e.pointerId) }
这段是第三步,用
addPointer的方法,指定需要处理的指针。
bind('MSGestureEnd', function(e){ var swipeDirectionFromVelocity = e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null if (swipeDirectionFromVelocity) { touch.el.trigger('swipe') touch.el.trigger('swipe'+ swipeDirectionFromVelocity) } })
接下来就是分析手势了,
Gesture里只处理
swipe事件。
velocityX和
velocityY分别为
x轴和
y轴上的速率。这里以
1或
-1为临界点,判断
swipe的方向。
如果
swipe的方向存在,则触发
swipe事件,同时也触发带方向的
swipe事件。
start
on('touchstart MSPointerDown pointerdown', function(e){ if((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0] if (e.touches && e.touches.length === 1 && touch.x2) { 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 if (delta > 0 && delta <= 250) touch.isDoubleTap = true touch.last = now longTapTimeout = setTimeout(longTap, longTapDelay) if (gesture && _isPointerType) gesture.addPointer(e.pointerId) })
过滤掉非触屏事件
if((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : e.touches[0]
这里还将
isPointerEventType的判断结果保存到了
_isPointerType中,用来判断是否为
PointerEvent。
这里的判断其实就是只处理
PointerEvent和
TouchEvent,并且
TouchEvent的
isPrimary必须为
true。
因为
TouchEvent支持多点触碰,这里只取触碰的第一点存入
firstTouch变量。
重置终点坐标
if (e.touches && e.touches.length === 1 && touch.x2) { touch.x2 = undefined touch.y2 = undefined }
如果还需要记录,终点坐标是需要更新的。
正常情况下,
touch对象会在
touchEnd或者
cancel的时候清空,但是如果用户自己调用了
preventDefault等,就可能会出现没有清空的情况。
这里有一点不太明白,为什么只会在
touches单点操作的时候才清空呢?多个触碰点的时候不需要清空吗?
记录触碰点的信息
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
now用来保存当前时间。
delta用来保存两次点击时的时间间隔,用来处理双击事件。
touch.el用来保存目标元素,这里有个判断,如果
target不是标签节点时,取父节点作为目标元素。这会在点击伪类元素时出现。
如果
touchTimeout存在,则清除定时器,避免重复触发。
touch.x1和
touch.y1分别保存
x轴坐标和
y轴坐标。
双击事件
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
可以很清楚地看到,
Zepto将两次点击的时间间隔小于
250ms时,作为
doubleTap事件处理,将
isDoubleTap设置为
true。
长按事件
touch.last = now longTapTimeout = setTimeout(longTap, longTapDelay)
将
touch.last设置为当前时间。这样就可以记录两次点击时的时间差了。
同时开始长按事件定时器,从上面的代码可以看到,长按事件会在
750ms后触发。
move
on('touchmove MSPointerMove pointermove', function(e){ if((_isPointerType = isPointerEventType(e, 'move')) && !isPrimaryTouch(e)) return firstTouch = _isPointerType ? e : 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) })
move事件处理了两件事,一是记录终点坐标,一是计算起点到终点之间的位移。
要注意这里还调用了
cancelLongTap清除了长按定时器,避免长按事件的触发。因为有移动,肯定就不是长按了。
end
on('touchend MSPointerUp pointerup', function(e){ if((_isPointerType = isPointerEventType(e, 'up')) && !isPrimaryTouch(e)) return cancelLongTap() if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) swipeTimeout = setTimeout(function() { if (touch.el){ touch.el.trigger('swipe') touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) } touch = {} }, 0) else if ('last' in touch) if (deltaX < 30 && deltaY < 30) { tapTimeout = setTimeout(function() { var event = $.Event('tap') event.cancelTouch = cancelAll if (touch.el) touch.el.trigger(event) if (touch.isDoubleTap) { if (touch.el) touch.el.trigger('doubleTap') touch = {} } else { touchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250) } }, 0) } else { touch = {} } deltaX = deltaY = 0 })
swipe
cancelLongTap() if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) || (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) swipeTimeout = setTimeout(function() { if (touch.el){ touch.el.trigger('swipe') touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2))) } touch = {} }, 0)
进入
end时,立刻清除
longTap定时器的执行。
可以看到,起点和终点的距离超过
30时,会被判定为
swipe滑动事件。
在触发完
swipe事件后,立即触发对应方向上的
swipe事件。
注意,
swipe事件并不是在
end系列事件触发时立即触发的,而是设置了一个
0ms的定时器,让事件异步触发,这个有什么用呢?后面会讲到。
tap
else if ('last' in touch) if (deltaX < 30 && deltaY < 30) { tapTimeout = setTimeout(function() { var event = $.Event('tap') event.cancelTouch = cancelAll if (touch.el) touch.el.trigger(event) }, 0) } else { touch = {} } deltaX = deltaY = 0
终于看到重点了,首先判断
last是否存在,从
start中可以看到,如果触发了
start,
last肯定是存在的,但是如果触发了长按事件,
touch对象会被清空,这时不会再触发
tap事件。
如果不是
swipe事件,也不存在
last,则只将
touch清空,不触发任何事件。
在最后会将
deltaX和
deltaY重置为
0。
触发
tap事件时,会在
event中加了
cancelTouch方法,外界可以通过这个方法取消所有事件的执行。
这里同样用了
setTimeout异步触发事件。
doubleTap
if (touch.isDoubleTap) { if (touch.el) touch.el.trigger('doubleTap') touch = {} }
这个
isDoubleTap在
start时确定的,上面已经分析过了,在
end的时候触发
doubleTap事件。
因此,可以知道,在触发
doubleTap事件之前会触发两次
tap事件。
singleTap
touchTimeout = setTimeout(function(){ touchTimeout = null if (touch.el) touch.el.trigger('singleTap') touch = {} }, 250)
如果不是
doubleTap,会在
tap事件触发的
250ms后,触发
singleTap事件。
cancel
.on('touchcancel MSPointerCancel pointercancel', cancelAll)
在接受到
cancel事件时,调用
cancelAll方法,取消所有事件的触发。
scroll
$(window).on('scroll', cancelAll)
从前面的分析可以看到,所有的事件触发都是异步的。
因为在
scroll的时候,肯定是只想响应滚动的事件,异步触发是为了在
scroll的过程中和外界调用
cancelTouch方法时, 可以将事件取消。
系列文章
读Zepto源码之代码结构读Zepto源码之内部方法
读Zepto源码之工具函数
读Zepto源码之神奇的$
读Zepto源码之集合操作
读Zepto源码之集合元素查找
读Zepto源码之操作DOM
读Zepto源码之样式操作
读Zepto源码之属性操作
读Zepto源码之Event模块
读Zepto源码之IE模块
读Zepto源码
3ff0
之Callbacks模块
读Zepto源码之Deferred模块
读Zepto源码之Ajax模块
读Zepto源码之Assets模块
读Zepto源码之Selector模块
参考
zepto touch 库源码分析PointerEvent
Pointer events
TouchEvent
Touch
GestureEvent
MSGestureEvent
一步一步DIY zepto库,研究zepto源码8--touch模块
zepto源码学习-06 touch
zepto源码之touch.js
addPointer method
License
署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:
作者:对角另一面
相关文章推荐
- 一步一步DIY zepto库,研究zepto源码8 -- touch模块
- Zepto源码之touch模块
- 读Zepto源码之Touch模块
- 读Zepto源码之Touch模块
- 一步一步DIY zepto库,研究zepto源码7 -- 动画模块(fx,fx_method)
- 读Zepto源码之Callbacks模块
- Zepto源码之fx模块
- zepto源码之touch.js
- Zepto源码(2016)——Zepto模块(核心模块)
- 读Zepto源码之Data模块
- 读Zepto源码之Event模块
- 读Zepto源码之Callbacks模块
- 读Zepto源码之Event模块
- Zepto源码之callback模块
- 移动端的silder,未封装,基于zepto的touch模块,有参照修改过touch的bug
- 一个普通的 Zepto 源码分析(二) - ajax 模块
- Zepto源码分析-ajax模块
- 读Zepto源码之Gesture模块
- zepto 事件模块源码分析
- 读Zepto源码之assets模块