简要:touch.js 主要提供滑动(swipe)与点击(tap:模拟click)的事件封装,针对手机常用浏览器(touchstart,touchmove,touchend)和IE10(msPointDown)的触摸事件兼容处理以及手势的事件处理。之所以封装touchstart形成tap是因为要解决点透问题,并且模拟click有双击与长按功能。
源码分析如下:
// Zepto.js
// (c) 2010-2016 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
;(function($){
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture
//(x1,y1)为开始点,(x2,y2)为结束点,判断滑动的方向
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 = {}
}
}
//取消长按事件,在longTap未执行前,longTapTimeout计时器不为null
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对象属性判断的语句
touch = {}
}
// 判断是否是第一个touch或pointer事件对象
// 检测主触点
// 参考链接:http://www.w3cplus.com/css3/adapting-your-webkit-optimized-site-for-internet-explorer-10.html
//如前面所说,指针事件模型为每个触点触发单独的事件。因此,如果你只想处理主触点(比如单指拖动的情况),
//你就需要在“move”和“up”处理之前使用下面这条语句过滤掉非主触点的触摸点:
function isPrimaryTouch(event){
return (event.pointerType == 'touch' ||
event.pointerType == event.MSPOINTER_TYPE_TOUCH)
&& event.isPrimary
}
//判断是否为IE指针事件,参照http://www.ayqy.net/blog/html5%E8%A7%A6%E6%91%B8%E4%BA%8B%E4%BB%B6/
function isPointerEventType(e, type){
return (e.type == 'pointer'+type ||
e.type.toLowerCase() == 'mspointer'+type)
}
$(document).ready(function(){
var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
//手势操作对象,在IE10中有效,参照http://www.jb51.net/html5/79175.html
if ('MSGesture' in window) {
gesture = new MSGesture()
gesture.target = document.body
}
$(document)
// 根据手势来判断滑动的方向,与后面根据距离来判断是不同的,
// 参照链接:http://www.th7.cn/web/js/201309/12944.shtml
.bind('MSGestureEnd', function(e){
//e.velocityX,e.velocityY,判断手势的方向
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){
if((_isPointerType = isPointerEventType(e, 'down')) &&
!isPrimaryTouch(e)) return
//获取第一个触点对象,若是指针对象,则是他自己
firstTouch = _isPointerType ? e : e.touches[0]
//清除掉之前触摸移动的touch数据,原因是因某些事件阻止默认行为,
// 导致touchcancel未被调用,touch数据未被清除
// TODO 具体是什么样的行为,有待具体分析
if (e.touches && e.touches.length === 1 && touch.x2) {
// Clear out touch movement data if we have it sticking around
// This can occur if touchcancel doesn't fire due to preventDefault, etc.
touch.x2 = undefined
touch.y2 = undefined
}
//获取当前时间点
now = Date.now()
//touch.last代表上一次触摸时间,delta标示两次触摸的间隔,用于判断是否为长按
delta = now - (touch.last || now)
//如果firstTouch.target是正常的dom元素,则赋值于touch.el,
//若是非正常元素,比如伪元素,则touch源是其父元素
touch.el = $('tagName' in firstTouch.target ?
firstTouch.target : firstTouch.target.parentNode)
//保证singleTouch计时器被清除
touchTimeout && clearTimeout(touchTimeout)
//获取touch点相对与page的位置
touch.x1 = firstTouch.pageX
touch.y1 = firstTouch.pageY
//手指双击的判断,如果是轻触,则delta为0
if (delta > 0 && delta <= 250) touch.isDoubleTap = true
//待参数都处理完后,将本次触点时间作为上一个次触点时间
touch.last = now
//默认按照长按事件处理
longTapTimeout = setTimeout(longTap, longTapDelay)
// adds the current touch contact for IE gesture recognition
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
cancelLongTap()
// swipe,根据距离判断是否为有效滑动
if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
(touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
//根据方向触发滑动事件
swipeTimeout = setTimeout(function() {
touch.el.trigger('swipe');
touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
touch = {}
}, 0)
// normal tap
// 若touch为空对象,表示为长按,接下来赋空值
else if ('last' in touch)
// last in touch 表明longTap未执行
// don't fire tap when delta position changed by more than 30 pixels,
// for instance when moving to a point and back to origin
if (deltaX < 30 && deltaY < 30) {
// delay by one tick so we can cancel the 'tap' event if 'scroll' fires
// ('tap' fires before 'scroll')
// 手指移动距离短,可认为是轻触tap
tapTimeout = setTimeout(function() {
// trigger universal 'tap' with the option to cancelTouch()
// (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
var event = $.Event('tap')
event.cancelTouch = cancelAll
touch.el.trigger(event)
// trigger double tap immediately
if (touch.isDoubleTap) {
if (touch.el) touch.el.trigger('doubleTap')
touch = {}
}
// trigger single tap after 250ms of inactivity
else {
touchTimeout = setTimeout(function(){
touchTimeout = null
if (touch.el) touch.el.trigger('singleTap')
touch = {}
}, 250)
}
}, 0)
} else {
touch = {}
}
deltaX = deltaY = 0
})
// when the browser window loses focus,
// for example when a modal dialog is shown,
// cancel all ongoing events
.on('touchcancel MSPointerCancel pointercancel', cancelAll)
// scrolling the window indicates intention of the user
// to scroll, not tap or swipe, so cancel all ongoing events
$(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)
我们先看一下其代码组织形式:
var touch = {},
touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
longTapDelay = 750,
gesture
//(x1,y1)为开始点,(x2,y2)为结束点,判断滑动的方向
function swipeDirection(x1, x2, y1, y2){}
//长按触发事件
function longTap(){}
//取消长按事件,在longTap未执行前,longTapTimeout计时器不为null
function cancelLongTap(){}
//清除所有类型的计时器,取消所有事件
function cancelAll() {}
function isPrimaryTouch(event){}
//判断是否为IE指针事件,参照http://www.ayqy.net/blog/html5%E8%A7%A6%E6%91%B8%E4%BA%8B%E4%BB%B6/
function isPointerEventType(e, type){}
$(document).ready(function(){})
;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
$.fn[eventName] = function(callback){ return this.on(eventName, callback) }
})
1:这里注册的事件都是自定义事件,回调与事件触发直接由zepto的event.js管理,这里,zepto将自定义事件的触发放在原生事件触发的回调函数中去,当用户点击屏幕的时候,会触发原生事件,而自定义的事件会在原生事件中被触发。
2:这里涉及到了一个技巧:那就是对于$.fn[eventName]事件注册,只是注册了自定义事件,而利用document来注册原生事件,但点击某个元素的时候,利用事件冒泡到document上,触发document上所注册的函数,在函数内部获取点击元素对象进而触发自定义事件。这样写既然代码重用了,又不必对每个元素都监听原生事件,减少了性能的开销,这是一个非常巧妙的方法。
3:还是一如既往的代码组织风格,先将工具尽量抽离出来,最后利用循环来统一来生产各个方法。
接下来看一下$(document).ready()里面的执行内容:
document的注册事件在文档加载完以后执行,这是为了保证文档加载完之前,只存储而不执行自定义事件。
MSGestrue:IE10中的手势对象,具体信息参考链接 : http://www.jb51.net/html5/79175.html,
接下来是document绑定MSGestrueEnd事件,判断手势的方向,进而触发滑动事件,关于如何运用MSGestrue,参考链接:http://www.th7.cn/web/js/201309/12944.shtml
这里主要介绍如何运用手势事件到元素的css上:
targetElement.addEventListener("MSGestureChange", manipulateElement);
function manipulateElement(e) {
// Uncomment the following code if you want to disable the built-in inertia provided by dynamic gesture recognition
// if (e.detail == e.MSGESTURE_FLAG_INERTIA)
// return;
var m = new MSCSSMatrix(e.target.style.transform); // Get the latest CSS transform on the element
e.target.style.transform = m
.translate(e.offsetX, e.offsetY) // Move the transform origin under the center of the gesture
.rotate(e.rotation * 180 / Math.PI) // Apply Rotation
.scale(e.scale) // Apply Scale
.translate(e.translationX, e.translationY) // Apply Translation
.translate(-e.offsetX, -e.offsetY); // Move the transform origin back
}
这里注册touchstart MSPointerDown pointerdown 事件,其大致功能是
1:判断是否为主触点,指针事件中每个触点都会触发事件,若不是则直接返回。2:获取有效触点对象,计算离上一次的触摸时间以便判断是否为双击
(双击的间隔时间一定比长按短)
3:获取事件源,默认作为长按处理。
接着注册touchmove MSPointerMove pointermove 事件,其大致功能是:
1:判断是否为主触点,指针事件中每个触点都会触发事件,若不是则直接返回。2:获取有效触点对象,取消长按定时器,3:计算滑动距离
最后注册touchend MSPointerUp pointerup 事件,其大致功能是:
1:判断是否为主触点,指针事件中每个触点都会触发事件,若不是则直接返回。2:取消长按定时器。3:根据滑动距离判断是否为有效滑动,若有效,则触发滑动事件
4:如果touch是空对象,则表示已经触发了长按事件。若没有,则表示在默认行为触发长按之前,手指离开屏幕,表明实际未发生长按行为。即发生了tap事件。触发tap的回调方法。但这里貌似未对点透事件做出处理(如果要处理,貌似要对事件做组织默认行为处理)。
那么如果在手指触摸或者滑动的过程中,失去了屏幕的焦点,或者用户滑动比较快,触发了滚动事件,则调用cancelAll,取消所有触摸行为。
// when the browser window loses focus,// for example when a modal dialog is shown,// cancel all ongoing events.on('touchcancel MSPointerCancel pointercancel', cancelAll)// scrolling the window indicates intention of the user// to scroll, not tap or swipe, so cancel all ongoing events$(window).on('scroll', cancelAll)
来源:https://www.cnblogs.com/zhutao/p/5866095.html