您的位置:首页 > Web前端 > JavaScript

zepto源码之touch.js

2017-08-03 09:42 253 查看

一、前言

touch.js主要提供滑动(swipe)与点击(tap:模拟click)的事件封装,针对手机常用浏览器(touchstart,touchmove,touchend)和IE10(msPointDown)

的触摸事件兼容处理以及手势的事件处理。

二、源码

//touch事件 在触摸设备上触发tap–和swipe–相关事件。这适用于所有的`touch`(iOS, Android)和`pointer`事件(Windows Phone)。
(function ($) {
var touch = {},
//延迟触发(ms): 250, 0, 0, 750
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture;

//判断滑动方向,返回Left, Right, Up, Down
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 = {}
}

//判断是否是点击指针是否为主指针(http://www.ayqy.net/blog/html5%E8%A7%A6%E6%91%B8%E4%BA%8B%E4%BB%B6/)
function isPrimaryTouch(event) {
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH)
&& event.isPrimary
}

//判断是否为鼠标事件或者ie的点击事件
function isPointerEventType(e, type) {
return (e.type == 'pointer' + type ||
e.type.toLowerCase() == 'mspointer' + type)
}

//加载完全时执行,将事件绑定到全局的document
$(document).ready(function () {
var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType;

//IE的手势
if ('MSGesture' in window) {
gesture = new MSGesture();
gesture.target = document.body
}

$(document)
.bind('MSGestureEnd', function (e) { //处理IE手势结束
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)
}
})
.on('touchstart MSPointerDown pointerdown', function (e) { //处理手指接触事件
//屏蔽掉非触摸设备(非触屏设备鼠标事件isPrimary属性为true)
if ((_isPointerType = isPointerEventType(e, 'down')) && !isPrimaryTouch(e)) return;
//记录起点坐标(_isPointerType用于ie)
firstTouch = _isPointerType ? e : e.touches[0];
//重置终点坐标(正常情况下touch会在touchend或cancelAll中清空,除了因为preventDefault等导致touchcancel不被触发)
if (e.touches && e.touches.length === 1 && touch.x2) {
touch.x2 = undefined;
touch.y2 = undefined
}
//当前时间毫秒数
now = Date.now();
//距离上次触碰的时间差
delta = now - (touch.last || now);
//点击元素 如果是正常的dom元素,则赋值于touch.el;若是非正常元素,比如伪元素,则touch源是其父元素
touch.el = $('tagName' in firstTouch.target ? firstTouch.target : firstTouch.target.parentNode);
//重置touch延迟事件处理器
touchTimeout && clearTimeout(touchTimeout);
//记录点击起点坐标
touch.x1 = firstTouch.pageX;
touch.y1 = firstTouch.pageY;
//判断是否双击
if (delta > 0 && delta <= 250) touch.isDoubleTap = true;
//将当期时间毫秒数存储到touch的last属性上
touch.last = now;
//注册长按延迟事件处理器
longTapTimeout = setTimeout(longTap, longTapDelay);
//支持IE手势识别
if (gesture && _isPointerType) gesture.addPointer(e.pointerId);
})
.on('touchmove MSPointerMove pointermove', function (e) { //处理手指滑动
if ((_isPointerType = isPointerEventType(e, 'move')) && !isPrimaryTouch(e)) return;
firstTouch = _isPointerType ? e : e.touches[0];
//取消长按事件处理器(touchstart设置的)
cancelLongTap();
//设置touch对象的位置
touch.x2 = firstTouch.pageX;
touch.y2 = firstTouch.pageY;
deltaX += Math.abs(touch.x1 - touch.x2);
deltaY += Math.abs(touch.y1 - touch.y2);
})
.on('touchend MSPointerUp pointerup', function (e) { //处理手指离开
if ((_isPointerType = isPointerEventType(e, 'up')) && !isPrimaryTouch(e)) return;
//取消长按事件处理器(touchstart设置的)
cancelLongTap();
//swipe 判定滑动动作(起点 - 终点的横向或者纵向距离超过30px)
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
//延迟执行所以可以在scroll事件触发时取消执行swipe事件
swipeTimeout = setTimeout(function () {
//根据方向触发滑动事件,并清空touch对象(所有touchend的回调触发自定义事件都作了判断是否touch对象有el属性,屏蔽比如滚动时同时触发了滑动事件等奇怪的现象)
if (touch.el) {
touch.el.trigger('swipe');
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
}
touch = {}
}, 0);
//一般情况下,touch是会存在last属性的;如果长按,那么touch会被赋空值,就不存在last属性了
else if ('last' in touch)
//当位置发生改变超过30px,不会被触发(比如移动到一个位置然后返回原先的位置)
if (deltaX < 30 && deltaY < 30) {
//立即准备执行轻触,不立即执行是为了scroll时能取消执行轻触(tap触发在scroll之前)
tapTimeout = setTimeout(function () {
// 触发全局tap,cancelTouch可以取消singleTap,doubleTap事件,以求更快响应轻触
var event = $.Event('tap');
event.cancelTouch = cancelAll;
// [by paper] fix -> "TypeError: 'undefined' is not an object (evaluating 'touch.el.trigger'), when double tap
if (touch.el) touch.el.trigger(event);
//立即触发doubleTap
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap');
touch = {}
}
// 250ms后触发singleTap,因为需要判断tap后250内是否还有tap,如果有则触发doubleTap;否则触发singleTap
else {
touchTimeout = setTimeout(function () {
//定时器已经执行了,没有必要clear
touchTimeout = null;
if (touch.el) touch.el.trigger('singleTap');
touch = {}
}, 250)
}
}, 0)
} else {
/**

bc7c
* 当位置距离总和deltaX或deltaY大于30,并且起点到终点距离小于30
* 即存在这样一种情况,移动到一个位置,然后返回原先的位置
* 则直接清空touch对象,并不执行任何操作
* */
touch = {}
}
//重置横向,纵向滑动距离
deltaX = deltaY = 0;
})
//当浏览器窗口失去焦点(如模态框弹窗显示),取消所有将要发生的事件
.on('touchcancel MSPointerCancel pointercancel', cancelAll);

//浏览器窗口滚动时,取消所有将要发生的事件,因为滚动window意味着用户的意图是滚动而不是tap或者swipe
$(window).on('scroll', cancelAll)
});

//绑定自定义回调函数到自定义事件上
['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function (eventName) {
$.fn[eventName] = function (callback) {
return this.on(eventName, callback)
}
})
})(Zepto);


三、源码分析

1、MSPointerDown/pointerdown

由于IE浏览器的触碰事件使用的是MSPointerDown/pointerdown等这样的鼠标事件来标识的,所以为了兼容IE浏览器也需要监听此类事件。在webkit内核的

移动端浏览器上touch.js可以正常运行,但是在chrome浏览器模拟移动端时会被触发两次,一次是监听到鼠标事件(pointerdown),一次是监听到touch事件

2、longTap

原生JS并没有提供longTap之类事件,所以zepto自己封装实现了此类事件。首先在touchstart时设置一个定时器750ms,如果在此区间没有触发touchmove、

touchend、touchcancel以及scroll事件,那么说明是长按事件,触发相应回调。

3、doubleTap

zepto同时也封装实现了doubleTap。在touchstart时设置延迟变量为这次与上次点击时间差,如果大于0(第一次点击为0)并且小于250,那么设置touch对象

双击标志量isDoubleTap为true,并在touchend触发doubleTap事件。

4、tap vs swipe

在touchend中判断起点坐标与终点坐标距离如果超过了30px,那么触发swipe事件,并触发swipe相应方向事件。tap需判断累计距离不超过30px,如果超过

30px可能存在移动到某个点再返回回来这种情况。

5、singleTap

在touchend中设置定时器250ms,如果在此区间再次点击则清掉定时器,否则触发singleTap事件。

6、scroll

窗口滚动时,可以监听到scroll事件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码