简要: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